From d738321dc0b5d907bfceebd3650b8d1830fab768 Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Sat, 23 Feb 2019 13:59:32 -0600 Subject: [PATCH] Use FSharpLu for JSON serialization (#24) --- src/api/MyPrayerJournal.Api/Data.fs | 15 ++++----------- .../MyPrayerJournal.Api.fsproj | 4 +++- src/api/MyPrayerJournal.Api/Program.fs | 14 +++++++++++++- src/app/package.json | 2 +- src/app/src/components/request/FullRequest.vue | 6 +++--- 5 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/api/MyPrayerJournal.Api/Data.fs b/src/api/MyPrayerJournal.Api/Data.fs index 0a7f235..bbfecc2 100644 --- a/src/api/MyPrayerJournal.Api/Data.fs +++ b/src/api/MyPrayerJournal.Api/Data.fs @@ -2,14 +2,7 @@ open FSharp.Control.Tasks.V2.ContextInsensitive open Microsoft.EntityFrameworkCore - -/// Helpers for this file -[] -module private Helpers = - - /// Convert any item to an option (Option.ofObj does not work for non-nullable types) - let toOption<'T> (x : 'T) = match box x with null -> None | _ -> Some x - +open Microsoft.FSharpLu /// Entities for use in the data model for myPrayerJournal [] @@ -247,7 +240,7 @@ type AppDbContext (opts : DbContextOptions) = member this.TryRequestById reqId userId = task { let! req = this.Requests.AsNoTracking().FirstOrDefaultAsync(fun r -> r.requestId = reqId && r.userId = userId) - return toOption req + return Option.fromObject req } /// Retrieve notes for a request by its ID and user ID @@ -262,7 +255,7 @@ type AppDbContext (opts : DbContextOptions) = member this.TryJournalById reqId userId = task { let! req = this.Journal.FirstOrDefaultAsync(fun r -> r.requestId = reqId && r.userId = userId) - return toOption req + return Option.fromObject req } /// Retrieve a request, including its history and notes, by its ID and user ID @@ -275,7 +268,7 @@ type AppDbContext (opts : DbContextOptions) = .Include(fun r -> r.history) .Include(fun r -> r.notes) .FirstOrDefaultAsync(fun r -> r.requestId = requestId && r.userId = userId) - match toOption fullReq with + match Option.fromObject fullReq with | Some _ -> return Some { req with history = List.ofSeq fullReq.history; notes = List.ofSeq fullReq.notes } | None -> return None | None -> return None diff --git a/src/api/MyPrayerJournal.Api/MyPrayerJournal.Api.fsproj b/src/api/MyPrayerJournal.Api/MyPrayerJournal.Api.fsproj index 343cec4..278fa5f 100644 --- a/src/api/MyPrayerJournal.Api/MyPrayerJournal.Api.fsproj +++ b/src/api/MyPrayerJournal.Api/MyPrayerJournal.Api.fsproj @@ -15,13 +15,15 @@ + + - + diff --git a/src/api/MyPrayerJournal.Api/Program.fs b/src/api/MyPrayerJournal.Api/Program.fs index 2e21bd3..3a118d3 100644 --- a/src/api/MyPrayerJournal.Api/Program.fs +++ b/src/api/MyPrayerJournal.Api/Program.fs @@ -4,11 +4,11 @@ open Microsoft.AspNetCore.Builder open Microsoft.AspNetCore.Hosting open System - /// Configuration functions for the application module Configure = open Giraffe + open Giraffe.Serialization open Giraffe.TokenRouter open Microsoft.AspNetCore.Authentication.JwtBearer open Microsoft.AspNetCore.Server.Kestrel.Core @@ -16,7 +16,9 @@ module Configure = open Microsoft.Extensions.Configuration open Microsoft.Extensions.DependencyInjection open Microsoft.Extensions.Logging + open Microsoft.FSharpLu.Json open MyPrayerJournal + open Newtonsoft.Json /// Set up the configuration for the app let configuration (ctx : WebHostBuilderContext) (cfg : IConfigurationBuilder) = @@ -30,6 +32,15 @@ module Configure = let kestrel (ctx : WebHostBuilderContext) (opts : KestrelServerOptions) = (ctx.Configuration.GetSection >> opts.Configure >> ignore) "Kestrel" + /// Custom settings for the JSON serializer (uses compact representation for options and DUs) + let jsonSettings = + let x = NewtonsoftJsonSerializer.DefaultSettings + x.Converters.Add (CompactUnionJsonConverter (true)) + x.NullValueHandling <- NullValueHandling.Ignore + x.MissingMemberHandling <- MissingMemberHandling.Error + x.Formatting <- Formatting.Indented + x + /// Configure dependency injection let services (sc : IServiceCollection) = use sp = sc.BuildServiceProvider() @@ -48,6 +59,7 @@ module Configure = opts.Audience <- jwtCfg.["Id"]) |> ignore sc.AddDbContext(fun opts -> opts.UseNpgsql(cfg.GetConnectionString "mpj") |> ignore) + .AddSingleton(NewtonsoftJsonSerializer jsonSettings) |> ignore /// Routes for the available URLs within myPrayerJournal diff --git a/src/app/package.json b/src/app/package.json index f606b81..f892714 100644 --- a/src/app/package.json +++ b/src/app/package.json @@ -1,6 +1,6 @@ { "name": "my-prayer-journal", - "version": "1.0.2", + "version": "1.1.0", "description": "myPrayerJournal - Front End", "author": "Daniel J. Summers ", "private": true, diff --git a/src/app/src/components/request/FullRequest.vue b/src/app/src/components/request/FullRequest.vue index 480a724..99bb6a7 100644 --- a/src/app/src/components/request/FullRequest.vue +++ b/src/app/src/components/request/FullRequest.vue @@ -15,7 +15,7 @@ article.mpj-main-content(role='main') tbody tr(v-for='item in log' :key='item.asOf') td {{ item.status }} on #[span.mpj-text-nowrap {{ formatDate(item.asOf) }}] - td(v-if='item.text').mpj-request-text {{ item.text.fields[0] }} + td(v-if='item.text').mpj-request-text {{ item.text }} td(v-else)   p(v-else) Loading request... @@ -52,11 +52,11 @@ export default { lastText () { return this.request.history .filter(hist => hist.text) - .sort(asOfDesc)[0].text.fields[0] + .sort(asOfDesc)[0].text }, log () { const allHistory = (this.request.notes || []) - .map(note => ({ asOf: note.asOf, text: { case: 'Some', fields: [ note.notes ] }, status: 'Notes' })) + .map(note => ({ asOf: note.asOf, text: note.notes, status: 'Notes' })) .concat(this.request.history) .sort(asOfDesc) // Skip the first entry for answered requests; that info is already displayed