Deserialize a nested JSON string in AWS Lambda (C#)

Viewed 3311

I have a nested JSON string that is passed to a lambda (part of a State Machine). I need to assign variables to each item in the dictionary which are then used further down the line in the Lambda process (not shown here). I have been able to write a couple of classes and a void to try and deserialize the json but I don't know how to integrate DeserialiseJSONString into the lambda handler FunctionHandler. My familiarity with Python does not seem to help with understanding how to utilize classes/functions here. Any input would be greatly appreciated. I am looking for the most simplistic approach possible (if that is possible). Here's my C# code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Amazon.Lambda.Core;

// Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class.
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]

namespace LambdaFunctionTwo
{
    public class Function
    {

        /// <summary>
        /// A simple hello world function.
        /// </summary>
        /// <param name="input"></param>
        /// <param name="context"></param>
        /// <returns></returns>
        public string FunctionHandler(NewUserInfo input, ILambdaContext context)
        {
            
            var Status = input.Status;
            var Platform = input.Platform;
            var GatewayID = input.GatewayID;
            var DatasourceID = input.DatasourceID;
            var Modulus = input.Modulus;
            var Exponent = input.Exponent;
            var Time = input.Time;

            LambdaLogger.Log($"Calling function name:   {context.FunctionName}\n");
            LambdaLogger.Log($"status:                  {Status}\n");
            LambdaLogger.Log($"platform:                {Platform}\n");
            LambdaLogger.Log($"gatewayID:               {GatewayID}\n");
            LambdaLogger.Log($"datasourceID:            {DatasourceID}\n");
            LambdaLogger.Log($"modulus:                 {Modulus}\n");
            LambdaLogger.Log($"exponent:                {Exponent}\n");
            LambdaLogger.Log($"time:                    {Time}\n");
            return $"{Platform} has been updated!";
        }
        public class NewUserInfo
        {
            public string Status { get; set; }
            public string Platform { get; set; }
            public string GatewayID { get; set; }
            public string DatasourceID { get; set; }
            public string Modulus { get; set; }
            public string Exponent { get; set; }
            public string Time { get; set; }

            public class user
            {
                public string Platform { get; set; }
                public string GatewayID { get; set; }
                public string DatasourceID { get; set; }
                public string Modulus { get; set; }
                public string Exponent { get; set; }
                public string Time { get; set; }
            }
        }

        public void DeserialiseJSONString(string strJSON)
        {
            var Credentials = JsonConvert.DeserializeObject<NewUserInfo>(strJSON);
            var Status = Credentials.Status;
            var Platform = Credentials.Platform;
            var GatewayID = Credentials.GatewayID;
            var DatasourceID = Credentials.DatasourceID;
            var Modulus = Credentials.Modulus;
            var Exponent = Credentials.Exponent;
            var Time = Credentials.Time;
        }
    }
}

And here is what an event being passed into the handler looks like:

{
  "status": "message processed",
  "credentials": {
    "platform": "database_platform",
    "gatewayID": "43534-534534g-23423423-1232",
    "datasourceID": "addr3024023-3e423423d-32423423-dfasd",
    "modulus": "thisisssomemodulus34242342342342342346453454125678765978",
    "exponent": "fakeexponent",
    "time": "2021-01-05 15:48:52.417653"
  }
}

Please be kind, C# is brand new to me!

EDIT:

Updated code (build is fine, test fails)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
// demo
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Amazon.Lambda.Core;

// Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class.
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
//[assembly: Amazon.Lambda.Core.LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))]

namespace LambdaFunctionTwo
{
    public class Function
    {

        /// <summary>
        /// A simple hello world function.
        /// </summary>
        /// <param name="input"></param>
        /// <param name="context"></param>
        /// <returns></returns>
        public string FunctionHandler(string input, ILambdaContext context)
        {
            var myDeserializedClass = JsonConvert.DeserializeObject<Root>(input);
            var Status = myDeserializedClass.Status;
            var Platform = myDeserializedClass.Credentials.Platform;
            var GatewayID = myDeserializedClass.Credentials.GatewayID;
            var DatasourceID = myDeserializedClass.Credentials.DatasourceID;
            var Modulus = myDeserializedClass.Credentials.Modulus;
            var Exponent = myDeserializedClass.Credentials.Exponent;
            var Time = myDeserializedClass.Credentials.Time;

            LambdaLogger.Log($"Calling function name:   {context.FunctionName}\n");
            LambdaLogger.Log($"status:                  {Status}\n");
            LambdaLogger.Log($"platform:                {Platform}\n");
            LambdaLogger.Log($"gatewayID:               {GatewayID}\n");
            LambdaLogger.Log($"datasourceID:            {DatasourceID}\n");
            LambdaLogger.Log($"modulus:                 {Modulus}\n");
            LambdaLogger.Log($"exponent:                {Exponent}\n");
            LambdaLogger.Log($"time:                    {Time}\n");
            return $"{myDeserializedClass} has been updated!";
        }
        public class Credentials
        {
            public string Platform { get; set; }
            public string GatewayID { get; set; }
            public string DatasourceID { get; set; }
            public string Modulus { get; set; }
            public string Exponent { get; set; }
            public string Time { get; set; }
        }

        public class Root
        {
            public string Status { get; set; }
            public Credentials Credentials { get; set; }
        }
    }
}

ERROR:

System.Exception: Error deserializing the input JSON to type String at Amazon.Lambda.TestTool.Runtime.LambdaExecutor.BuildParameters(ExecutionRequest request, ILambdaContext context) in C:\codebase\aws-lambda-dotnet\Tools\LambdaTestTool\src\Amazon.Lambda.TestTool\Runtime\LambdaExecutor.cs:line 214
   at Amazon.Lambda.TestTool.Runtime.LambdaExecutor.ExecuteAsync(ExecutionRequest request) in C:\codebase\aws-lambda-dotnet\Tools\LambdaTestTool\src\Amazon.Lambda.TestTool\Runtime\LambdaExecutor.cs:line 52
---------------- Inner 1 Exception ------------
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
   at Amazon.Lambda.TestTool.Runtime.LambdaExecutor.BuildParameters(ExecutionRequest request, ILambdaContext context) in C:\codebase\aws-lambda-dotnet\Tools\LambdaTestTool\src\Amazon.Lambda.TestTool\Runtime\LambdaExecutor.cs:line 210
---------------- Inner 2 Exception ------------
Amazon.Lambda.Serialization.SystemTextJson.JsonSerializerException: Error converting the Lambda event JSON payload to a string. JSON strings must be quoted, for example "Hello World" in order to be converted to a string: The JSON value could not be converted to System.String. Path: $ | LineNumber: 0 | BytePositionInLine: 1.
   at Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer.Deserialize[T](Stream requestStream)
---------------- Inner 3 Exception ------------
System.Text.Json.JsonException: The JSON value could not be converted to System.String. Path: $ | LineNumber: 0 | BytePositionInLine: 1.
   at System.Text.Json.ThrowHelper.ReThrowWithPath(ReadStack& readStack, Utf8JsonReader& reader, Exception ex)
   at System.Text.Json.JsonSerializer.ReadCore(JsonSerializerOptions options, Utf8JsonReader& reader, ReadStack& readStack)
   at System.Text.Json.JsonSerializer.ReadCore(Type returnType, JsonSerializerOptions options, Utf8JsonReader& reader)
   at System.Text.Json.JsonSerializer.ParseCore(ReadOnlySpan`1 utf8Json, Type returnType, JsonSerializerOptions options)
   at System.Text.Json.JsonSerializer.Deserialize[TValue](ReadOnlySpan`1 utf8Json, JsonSerializerOptions options)
   at Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer.Deserialize[T](Stream requestStream)
---------------- Inner 4 Exception ------------
System.InvalidOperationException: Cannot get the value of a token type 'StartObject' as a string.
   at System.Text.Json.Utf8JsonReader.GetString()
   at System.Text.Json.Serialization.Converters.JsonConverterString.Read(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options)
   at System.Text.Json.JsonPropertyInfoNotNullable`4.OnRead(ReadStack& state, Utf8JsonReader& reader)
   at System.Text.Json.JsonPropertyInfo.Read(JsonTokenType tokenType, ReadStack& state, Utf8JsonReader& reader)
   at System.Text.Json.JsonSerializer.ReadCore(JsonSerializerOptions options, Utf8JsonReader& reader, ReadStack& readStack)
1 Answers

I'll summarise the question then explain the steps to how I solved this. It's not hard, but there's some tricks newbies might miss.

Question:

Given some json string, how can I easily deserialize the json string into a class (with children/nested classes, etc)

Answer:

If we have the json schema (which you did provide), we need to do the following:

  • Create some classes (which the data will be persisted too)
  • Get the json text
  • Deserialize
  • Test/Assert.

1 - Create the classes.

Exactly like @chetanRanpariya said, let's use a website that generates these for us. Sure, having a small json might be trivial, but when you get a large json schema, then this saves you HEAPS of time.

Head over to https://json2csharp.com/ and paste your json schema in there, then ask that site to generate the output.

Even in the output they include a sample line of code to actually deserialize this!

enter image description here

The root class is a crap name, so I usually rename it to what the json schema (data) is about. For this example, I'll leave it .. but I would have renamed this to message or something.

Okay, so we have our classes generated as C# code .. now let's have a play.

2 Test code

To test out simple C# code, I next use another website called DotNet Fiddler.

In this current "fiddle" you can see that I:

  • setup the json message
  • deserialize this message into a root class.

and the Newtonsoft nuget library is smart enough to figure everything out.

Let's have a look at what I did, so you don't miss out on the tricks, to get this working:

enter image description here

enter image description here

In order of the things I did

  • Changed it to NET5.0
  • Added Newtonsoft.Json nuget library (latest version)
  • Copy/Pasted the classes over from Json2CSharp. Notice how I've put them just under the Main method, here?
  • Added that one line of code from Json2CSharp -> Root myDeserializedClass = JsonConvert.DeserializeObject<Root>(myJsonResponse);
  • Noticed that the code doesn't understand JsonConvert, so I added that namespace at the top of the file -> using Newtonsoft.Json;
  • Now, created my fake json text and then passed that value into that deserialize method (notice the name of variable in the method name is (json)
  • Created a few more lines of code to print out the values of the class which the json was deserialized to.

and voila! we have some json deserialized and working.

Newbie trick: Notice the weird json string? how it is: var json = @" ... that @ makes the string a MULTI-LINE string (also called a verbatim string literal or something). When you do this, it's easier to read large swaths of string data over multi lines .. but we have to double-quote any " (quotes). Normally, we escape a quote inside a string (e.g. \") but multi-lines don't like any escaping. It's a literal string .. hence the name.

Related