How to verify stringified json in pact - json

I am trying to build a pact between two services using asynchronous communication.
This is the code I used for generate the pact:
#ExtendWith(PactConsumerTestExt.class)
#PactTestFor(providerName = "provider", providerType = ProviderType.ASYNCH)
public class StringifiedPactTest {
#Pact(consumer = "consumer", provider = "provider")
public MessagePact generatePact(MessagePactBuilder builder) {
return builder.hasPactWith("provider")
.expectsToReceive("A valid aws sns event")
.withContent(new PactDslJsonBody().stringType(new String[]{"MessageId", "TopicArn"}).stringValue("Message", new PactDslJsonBody().stringType("Value", "Foo").toString()))
.toPact();
}
#Test
#PactTestFor(pactMethod = "generatePact")
public void buildPact(List<Message> messages) {
}
}
And the generated pact is
{
"consumer": {
"name": "consumer"
},
"provider": {
"name": "provider"
},
"messages": [
{
"description": "A valid aws sns event",
"metaData": {
"contentType": "application/json"
},
"contents": {
"TopicArn": "string",
"Message": "{\"Value\":\"Foo\"}",
"MessageId": "string"
},
"matchingRules": {
"body": {
"$.MessageId": {
"matchers": [
{
"match": "type"
}
],
"combine": "AND"
},
"$.TopicArn": {
"matchers": [
{
"match": "type"
}
],
"combine": "AND"
}
}
}
}
],
"metadata": {
"pactSpecification": {
"version": "3.0.0"
},
"pact-jvm": {
"version": "4.0.10"
}
}
}
This means the producer should have a "Message" that matches {"Value" : "Foo"}, any other combination like {"Value" : "Bar" } won't be successful.
Is there any way to add matching rules inside a stringified json?
Thanks!

Here's an anonymised example from a test we have. Hope it's useful. This creates a pact that matches only on type. So on the provider side, when I test against the contract, it doesn't matter what value I have for categoryName for example, as long as it's a stringType:
#PactTestFor(providerName = "provider-service", providerType = ProviderType.ASYNCH)
public class providerServiceConsumerPactTest {
private static String messageFromJson;
#BeforeAll
static void beforeAll() throws Exception {
messageFromJson = StreamUtils.copyToString(new ClassPathResource("/json/pact/consumer-service_provider-service.json").getInputStream(), Charset.defaultCharset());
}
#Pact(provider = "provider-service", consumer = "consumer-service")
public MessagePact providerServiceMessage(MessagePactBuilder builder) {
DslPart body = new PactDslJsonBody()
.object("metaData")
.stringType("origin", "provider-service")
.datetimeExpression("dateCreated", "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
.closeObject()
.minArrayLike("categories", 0, 1)
.stringType("id", "example data")
.stringType("categoryName", "example data")
.booleanType("clearance", false)
.closeObject()
.closeArray();
return builder
.expectsToReceive("a provider-service update")
.withContent(body)
.toPact();
}
#Test
#PactTestFor(pactMethod = "providerServiceMessage")
public void testProviderServiceMessage(MessagePact pact) {
// State
final String messageFromPact = pact.getMessages().get(0).contentsAsString();
// Assert
JSONAssert.assertEquals(messageFromPact, messageFromJson, false);
}

I'm having exactly the same issue, and unfortunately I don't think it's possible to tell Pact to parse the stringified JSON and look inside it (e.g. to verify that parse(Message).Value === "Foo" in your example).
The best you can do is write a regular expression to match the string you're expecting. This kind of sucks because there's no easy way to ignore the ordering of the JSON keys (e.g. "{\"a\":\"1\", \"b\":\"2\"}" and "{\"b\":\"2\", \"a\":\"1\"}" will compare different) but AFAIK Pact simply lacks the parsing functionality we're looking for, so the only tool it provides is regex.

Related

How to use Rest Template to step into a JSON object?

I have learned how to use a rest template to access a JSON array like this:
[
{
"ID": "0ae6496f-bb0b-4ebd-a094-ca766e82f3e7",
"Confirmed": 0,
}
{
"ID": "e010ced5-c7cb-4090-a7ed-206f4c482a5b",
"Confirmed": 0,
}
]
I accessed the Confirmed for example with
public Model[] getModel() {
ResponseEntity<Model[]> response = restTemplate.getForEntity(apiUrl, Model[].class);
return response.getBody();
}
but now I have to access data in another Json from another API. The data looks like this
{
"prefixes": [
{
"region": "ap-northeast-2",
"service": "AMAZON",
},
{
"region": "eu-west-3",
"service": "AMAZON",
}
]
}
How can I access region or service inside, and what would be the proper name for this?
The first is a JSON array, the second a JSON object?
The first API is simply
https://example.com
Whereas the second is
https://example.com/data.json.
You have to create the POJO for return type:
List<RegionServiceObject> items;
Where RegionServiceObject looks:
public class RegionServiceObject {
private String region;
private String service;
// constructors, getters/setters, toString()....
}
The way of deseariliseing to object is similar to which you already wrote:
RegionServiceObject[] items = restTemplate.getForObject(url, RegionServiceObject[].class);
and access for specific item will be as usual for specific item:
for (RegionServiceObject item : items) {
item.getRegion();
item.getService();
// use them here
}

Contentful .NET SDK Newtonsoft.Json.JsonReaderException Deserializing JSON to POCO

Update 1 8.July.2020: Maybe I should not have started with the Rich Text field type, which seems to take quite a bit of additional work to access:
https://www.contentful.com/developers/docs/net/tutorials/rich-text/
Update 2 9.July.2020: No wonder it cannot serialize Rich Text to a string. Look how it stores the Rich Text:
"fields": {
"richTextField": {
"nodeType": "document",
"data": {},
"content": [
{
"nodeType": "paragraph",
"content": [
{
"nodeType": "text",
"value": "This is the ",
"marks": [],
"data": {}
},
{
"nodeType": "text",
"value": "rich text",
"marks": [
{
"type": "underline"
}
],
"data": {}
},
{
"nodeType": "text",
"value": " field.",
"marks": [],
"data": {}
}
],
"data": {}
}
]
},
This doesn't look fun to reassemble into HTML. If I wanted it broken down, I would use HtmlAgilityPack.
Update 3 9.July.2020:
Actually, this doesn't make any sense. This documentation suggests that my models use this Contentful Document class in my POCOs and that they should contain additional Contentful-specific code for accessing data.
One of the main goals of headless CMS is to avoid vendor-specific APIs (lock-in). The way that works is that the CMS just hydrates POCOs, basically deserializing from JSON. No other headless CMS is going to populate a structure like this from Rich Text, so just using this crazy format for HTML leads to vendor-lock-in or the need for a major code overhaul just to change the CMS (not just changing the POCO hydration logic, but changing the POCOs themselves). Not to mention the JSON payload and CPU load impact of structuring HTML like this. In any case, with the Contentful architecture just for Rich Text Fields (probably one of the most common elements in a CMS), there is a bunch of vendor-specific coding just to get the value and the front-end becomes way too aware of the CMS. So I would have to deserialize to some kind of intermediary objects and then use those to populate my view models. Or I could replicate all of the logic around Document into my own project, but that sounds almost worse.
I don't see how anyone can use this system. Please correct my misperceptions.
Original thread
I posted this on https://www.contentfulcommunity.com/, but I don't know how to get that post through the moderators.
I try to follow these instructions for the Contentful .NET SDK to access an Entry:
https://github.com/contentful/contentful.net
I may be using newer .NET Core, but it doesn’t compile that way. This compiles:
using Contentful.Core;
using Contentful.Core.Configuration;
using System;
using System.Net.Http;
namespace CfClt
{
class Program
{
public class Entry
{
public string richTextField { get; set; }
}
static void Main(string[] args)
{
HttpClient httpClient = new HttpClient();
ContentfulOptions options = new ContentfulOptions
{
DeliveryApiKey = "A",
PreviewApiKey = "B",
SpaceId = "C"
};
ContentfulClient client = new ContentfulClient(httpClient, options);
Entry entry = client.GetEntry<Entry>("4SVaB1ps4H6Ml9ZxWsCWOn").GetAwaiter().GetResult();
Console.WriteLine(entry.richTextField);
}
}
}
But I get an exception deserializing from JSON to the POCO. Here is the JSON for the Entry:
{
"sys": {
"space": {
"sys": {
"type": "Link",
"linkType": "Space",
"id": "qjiunow8a0ig"
}
},
"id": "4SVaB1ps4H6Ml9ZxWsCWOn",
"type": "Entry",
"createdAt": "2020-07-08T19:52:47.34Z",
"updatedAt": "2020-07-08T19:52:47.34Z",
"environment": {
"sys": {
"id": "master",
"type": "Link",
"linkType": "Environment"
}
},
"revision": 1,
"contentType": {
"sys": {
"type": "Link",
"linkType": "ContentType",
"id": "firstContentType"
}
},
"locale": "en-US"
},
"fields": {
"richTextField": {
"nodeType": "document",
"data": {},
"content": [
{
"nodeType": "paragraph",
"content": [
{
"nodeType": "text",
"value": "this is the rich text field. What is my ID?",
"marks": [],
"data": {}
}
],
"data": {}
}
]
}
}
}
This is the exception:
Unhandled exception. Newtonsoft.Json.JsonReaderException: Error reading string. Unexpected token: StartObject. Path 'fields.richTextField', line 32, position 22.
at Newtonsoft.Json.JsonReader.ReadAsString()
at Newtonsoft.Json.JsonReader.ReadForType(JsonContract contract, Boolean hasConverter)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent)
at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType)
at Newtonsoft.Json.JsonSerializer.Deserialize(JsonReader reader, Type objectType)
at Newtonsoft.Json.Linq.JToken.ToObject(Type objectType, JsonSerializer jsonSerializer)
at Newtonsoft.Json.Linq.JToken.ToObject[T](JsonSerializer jsonSerializer)
at Contentful.Core.ContentfulClient.GetEntry[T](String entryId, String queryString, CancellationToken cancellationToken) in C:\temp\cfclt\CfClt\Contentful.Core\ContentfulClient.cs:line 136
at CfClt.Program.Main(String[] args) in c:\temp\cfclt\CfClt\CfClt\Program.cs:line 26
using Contentful.Core;
using Contentful.Core.Configuration;
using Contentful.Core.Models;
using Deliverystack.Core.Fulcontent.Models.Repositories;
using System;
using System.Net.Http;
namespace cfclt
{
class Program
{
public class Entry
{
public string StringField { get; set; }
public string Url { get; set; }
public Document RichTextField { get; set; }
}
static void Main(string[] args)
{
HttpClient httpClient = new HttpClient();
ContentfulOptions options = new ContentfulOptions
{
DeliveryApiKey = "A",
PreviewApiKey = "B",
SpaceId = "C"
};
ContentfulClient client = new ContentfulClient(httpClient, options);
ContentfulRepository repository = new ContentfulRepository(client);
Entry entry = repository.Get<Entry>("/");
Console.WriteLine(entry + " : " + entry.StringField + " : " + entry.Url + " : " + new HtmlRenderer().ToHtml(entry.RichTextField).GetAwaiter().GetResult());
}
}
}
using Contentful.Core;
using Contentful.Core.Models;
using Contentful.Core.Search;
using Deliverystack.Core.Models.Repositories;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Deliverystack.Core.Fulcontent.Models.Repositories
{
public class ContentfulRepository : IRepository
{
private ContentfulClient _client;
public ContentfulRepository(ContentfulClient client)
{
_client = client;
}
public override TEntryModel Get<TEntryModel>(string entryIdentifier, string contentTypeUid = null)
{
TEntryModel result = null;
if (!entryIdentifier.StartsWith("/"))
{
result = _client.GetEntry<TEntryModel>(entryIdentifier).GetAwaiter().GetResult();
}
else
{
List<string> contentTypes = new List<string>();
if (contentTypeUid != null)
{
contentTypes.Add(contentTypeUid);
}
else
{
foreach (ContentType doNotUseCt in _client.GetContentTypes().GetAwaiter().GetResult())
{
ContentType myContentType = doNotUseCt;
contentTypes.Add(myContentType.SystemProperties.Id);
}
}
foreach(string doNotUseCti in contentTypes)
{
string myContentTypeUid = doNotUseCti;
QueryBuilder<TEntryModel> queryBuilder = QueryBuilder<TEntryModel>.New.ContentTypeIs(myContentTypeUid).FieldEquals("fields.url", entryIdentifier);
ContentfulCollection<TEntryModel> entries = _client.GetEntries(queryBuilder).GetAwaiter().GetResult();
if (entries.Count() > 0)
{
result = entries.Items.First();
}
}
}
return result;
}
Actually this is a bit non-deterministic because multiple entries in multiple content types could have a common URL, in which case this would return one of them, but would those other threads possibly override result afterwords, changing the data returned to the caller after this method has already returned that data? Maybe I need to create threads explicitly and cancel those threads on the first match.

.net Core and Serilog Email sink - json config

I'm using .net Core 2.0 and Serilog Email sink. I have problem to configure email sink with appsettings.json. The same configuration from program.cs is working while one from appsetting.json isn't.
The settings system (ReadFrom.Configuration()) really only does try to call methods and extension methods that it can discover and pass arguments provided from the configuration file.
Unfortunately, it only supports basic types for the time being (convertible to/from string and a few more specific cases) and therefore, parameters of type EmailConnectionInfo cannot be provided.
As a workaround, though, if you only need to pass in a few parameters, you can create your own extension method that accepts the parameters that you need and call it from the configuration system.
In your case, you would need to do the following :
First, define an extension method EmailCustom(...) that can be plugged on WriteTo (which is of type Serilog.Configuration.LoggerSinkConfiguration) and returns a LoggerConfiguration.
This would look something like (not tested, no usings etc :P) :
namespace Serilog{
public static class MyCustomExtensions
{
public static LoggerConfiguration EmailCustom(this LoggerSinkConfiguration sinkConfiguration, string param1, int param2, LogEventLevel restrictedToMinimumLevel){
// the actual call to configure the Email sink, passing in complex parameters
return sinkConfiguration.Email(... ... , restrictedToMinimumLevel , EmailConnectionInfo(){
Foo = "bar",
Baz = param1,
Qux = param2,
}
);
}
}
}
From that point on, you should be able to write C# code like :
new LoggerConfiguration()
.WriteTo.EmailCustom(param1: "my param1", param2: 42)
// ...
.CreateLogger();
Once you have that working, you can actually define that method call in json thanks to Serilog.Settings.Configuration
in that case, that would look like
{
"Serilog": {
"Using" : ["TheNameOfTheAssemblyThatContainsEmailCustom"],
"MinimumLevel": "Debug",
"WriteTo": [
{
"Name": "EmailCustom",
"Args": {
"param1": "my param1",
"param2": 42,
"restrictedToMinimumLevel": "Verbose"
}
}]
}
}
This strategy can be applied for other sinks and other configuration parts of Serilog as well.
You can find a bit more about the configuration system here :
the project's README Serilog.Settings.Configuration
examples of what can be done through configuration (shameless plug :p)
For others Like me that have trouble piecing things between the lines here is a complete answer using the framework presented by tsimbalar for a solution that sends email out using SendGrid.
I added the following class to the root of my project ("MyApp"). This gets called automatically from the ReadFrom.Configuration(configuration).CreateLogger(); due to the WriteTo EmailCustom in the appsettings.
using System;
using System.Net;
using Serilog;
using Serilog.Configuration;
using Serilog.Events;
using Serilog.Sinks.Email;
namespace TrackumApi
{
public static class SerilogEmailExtension
{
public static LoggerConfiguration EmailCustom(this LoggerSinkConfiguration sinkConfiguration,
string fromEmail,
string toEmail,
string enableSsl,
string mailSubject,
string isBodyHtml,
string mailServer,
string networkCredentialuserName,
string networkCredentialpassword,
string smtpPort,
string outputTemplate,
string batchPostingLimit,
string periodMinutes,
string restrictedToMinimumLevel)
{
return sinkConfiguration.Email(new EmailConnectionInfo
{
FromEmail = fromEmail,
ToEmail = toEmail,
EnableSsl = GetBoolean(enableSsl),
EmailSubject = mailSubject,
IsBodyHtml = GetBoolean(isBodyHtml),
MailServer = mailServer,
NetworkCredentials = new NetworkCredential(networkCredentialuserName, networkCredentialpassword),
Port = GetInt(smtpPort)
}, outputTemplate, GetLevel(restrictedToMinimumLevel),
GetInt(batchPostingLimit), TimeSpan.FromMinutes(GetInt(periodMinutes))
);
}
//The system hated converting the string inputs inline so I added the conversion methods:
private static int GetInt(string instring)
{
return int.TryParse(instring, out var result) ? result : 0;
}
private static bool GetBoolean(string instring)
{
return bool.TryParse(instring, out var result) && result;
}
private static LogEventLevel GetLevel(string restrictedtominimumlevel)
{
return Enum.TryParse(restrictedtominimumlevel, true,
out LogEventLevel level) ? level : LogEventLevel.Warning;
}
}
}
In my origianl post I modified my Program.cs but it turns out that is not needed. However the addition of the Serilog.Debugging.SelfLog before any other code is still priceless:
Serilog.Debugging.SelfLog.Enable(Console.Out);
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", true, true)
.Build();
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(configuration)
.CreateLogger();
Finally I modified the appsettings.json as follows (forgive the extra, but I think that might also help somebody):
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"Serilog": {
"Using": [ "Serilog", "Serilog.Sinks.Console", "Serilog.Sinks.File", "MyApp" ],
"MinimumLevel": {
"Default": "Verbose",
"Override": {
"Microsoft": "Warning",
"System": "Warning",
"Microsoft.AspNetCore.Authentication": "Information"
}
},
"WriteTo": [
{
"Name": "Console",
"Args": {
"outputTemplate": "[{Timestamp:HH:mm:ss.fff} [{Level}] {SourceContext} {Message}{NewLine}{Exception}",
"theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console"
}
},
{
"Name": "File",
"Args": {
"path": "C:\\Temp\\Logs\\MyApp.log",
"fileSizeLimitBytes": 1000000,
"rollOnFileSizeLimit": "true",
"shared": "true",
"flushToDiskInterval": 3,
"outputTemplate": "[{Timestamp:MM/dd/yy HH:mm:ss} [{Level}] {SourceContext} {Message}{NewLine}{Exception}",
"restrictedToMinimumLevel": "Verbose"
}
},
{
"Name": "EmailCustom",
"Args": {
"fromEmail": "no-reply#mydomain.com",
"toEmail": "me#mydomain.com",
"enableSsl": false,
"mailSubject": "MyApp Message",
"isBodyHtml": true,
"mailServer": "smtp.sendgrid.net",
"networkCredentialuserName": "mysendgridusername",
"networkCredentialpassword": "mysendgridpassword",
"smtpPort": 587,
"outputTemplate": "[{Timestamp:HH:mm:ss.fff} {Level:u3}] {Message:lj} <s:{SourceContext}>{NewLine}{Exception}",
"batchPostingLimit": 10,
"periodMinutes": 5,
"restrictedToMinimumLevel": "Verbose"
}
}
],
"Enrich": [ "FromLogContext" ],
"Properties": {
"Application": "MyApp"
}
}
}
HTH!

How to remove Task json properties in Nancy.Response.AsJson

I've made one of my API endpoints and inner logic asynchronous and when previously I've used Response.AsJson(Foo.bar()) , it would return the json representation normally, but now I see this appended to it:
{
"result": [
{
"id": "59d680cc734d1d08b4e6c89c",
"properties": {
"name": "value"
}
}
],
"id": 3,
"exception": null,
"status": 5,
"isCanceled": false,
"isCompleted": true,
"isCompletedSuccessfully": true,
"creationOptions": 0,
"asyncState": null,
"isFaulted": false
}
But I want it to be like this:
"id": "59d680cc734d1d08b4e6c89c",
"properties": {
"name": "value"
}
As I understand, it's because I've wrapped my object in a Task , but I can't figure out, how with Nancy framework, which I use the Response.AsJson, to make it so the properties are excluded. I can obviously omit the Response.AsJson of the returned object, but then response is no longer Json if requesting through web-browser for example.
For further example
NancyModule for routing API:
public ItemCatalogModule(IItemCatalog itemCatalog) : base("/itemCatalog")
{
Get("/fetch/{id}", async parameters =>
{
var id = (string) parameters.id;
var response = await Response.AsJson(itemCatalog.GetItem(id));
return response;
});
}
How the interface looks like of ItemCatalog:
public interface IItemCatalog
{
Task<Item> GetItem(string id);
}
You shoud do this :
public ItemCatalogModule(IItemCatalog itemCatalog) : base("/itemCatalog")
{
Get("/fetch/{id}", async parameters =>
{
var id = (string) parameters.id;
return Response.AsJson(await itemCatalog.GetItem(id));
});
}

How to get json data into apex salesforce?

I have a json like this, which i am getting in the response from http call
{
"offset": 0,
"limit": 50,
"objects": [
{
"id": "59118fb6e4b0168ec4b56692",
"modifiedDate": 1494323126886,
"requestedIds": null,
"mergedIds": [],
"properties": {
"name": [
{
"value": "Abhimanyu",
"metadata": {}
}
],
"company": [],
"title": [],
"email": [
{
"value": "absinghrathore127#gmail.com",
"metadata": {}
}
]
},
"state": "ACTIVE"
},
{
"id": "590d5813e4b03a8336fa1642",
"modifiedDate": 1494046739619,
"requestedIds": null,
"mergedIds": [],
"properties": {
"name": [
{
"value": "Tim Archer",
"metadata": {}
}
],
"company": [],
"title": [],
"email": [
{
"value": "tim#avocado.com",
"metadata": {}
}
]
},
"state": "ACTIVE"
}
],
"size": 2
}
and i am able to get objects from json via this following code :
String s = res.getBody();
Map<String,Object> jsonMap = (Map<String, Object>)JSON.deserializeUntyped(s);
String jsonSubset = JSON.serialize(jsonMap.get('objects'));
What i need is the value of name and email in some variable.
Please help me out in this!!
This is going to be a tedious task but once you've classified your all data into appropriate Wrapper classes then it's fairly simple and easy to maintain.
First thing is to define your MainWrapper class. This will contain all the at it's own level. If it has any Object as key-pair then we need to make sure to include it as a List<>. So This is how your MainWrapper should be:
public class MainWrapper {
Integer offset; // Singleton variable
Integer limits; // Singleton variable
List<ObjectsWrapper> objects; // Collection variable since it starts with [],
Integer size; // Singleton variable
}
Since you've array of objects in JSON that's why I've included it as a List in MainWrapper. Now it's time to define ObjectsWrapper. Below is wrapper defined for the same.
public class ObjectsWrapper {
String id;
String modifieddate;
String requestedIds;
PropertyWrapper properties;
}
Since there is only on properties associated with objects that's why it's a non-collection type. Below is representation of properties.
public class PropertyWrapper {
List<NameWrapper> name;
List<String> company;
List<String> title;
List<EmailWrapper> email;
String state;
}
public class NameWrapper {
String name;
String metadata;
}
I guess now you've a fair idea of how to organize data of JSON into various wrapper class. Once you're done with this, simply deserialize the JSON into MainWrapper class and access it.
For example:
MainWrapper mainJSONWrapper = (MainWrapper) JSON.deserialize(JSON,MainWrapper.class);
List<ObjectsWrapper> objectsLst = mainJSONWrapper.objects;
for(ObjectsWrapper obj:objectsLst) {
List<NameWrapper> lstNameWrapper = obj.properties;
for(NameWrapper nameObj:NameWrapper) {
System.debug('Name:'+nameObj.name);
System.debug('metadata:'+nameObj.metadata);
}
}
Above code is not tested but yes, it will give idea how you should deserialize JSON in appropriate manner.
Also go through this answer..How to deserialize a JSON String to Apex