Is it possible to deserialize inline arrays with Json.NET? - json.net

Let me try to simplify my question by extracting and abstracting relevant information from a complex object and large JSON text.
Class Foo()
{
string Name;
Version[] versions;
}
JsonText(converted from XML) can be something like this:
"Foo": {
"Name": "test",
"versions": {
"Major": "1",
"Minor": "1"
},
"versions": {
"Major": "2",
"Minor": "1"
},
"versions": {
"Major": "3",
"Minor": "1"
}
}
or the following:
"Foo": {
"Name": "test",
"versions": {
"Major": "1",
"Minor": "1"
}
}
JsonConvert.DeserializeObject(JsonText) results in:
Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'Version[]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly.
Is it possible to do this desrialization?

Updated based on commment
Your Json doesn't look valid. A Json needs to be enclosed with "[" "]". For example,
[ "Ford", "BMW", "Fiat" ]
If you are allowed to modify your Json, You could modify the Json as following
{
"Foo":
{
"Name": "test",
"versions": [
{
"Major": "1",
"Minor": "1"
},
{
"Major": "2",
"Minor": "1"
},
{
"Major": "3",
"Minor": "1"
}]
}
}
Based on your comment, you would like to avoid the Json Array Syntax if there is only a single element. On other cases, you would like to stick to Array Syntax. In such scenario,you could write a Custom Json Converter.
class CustomConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(List<T>));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
if (token.Type == JTokenType.Array)
{
return token.ToObject<List<T>>().ToArray();
}
return new List<T> { token.ToObject<T>() }.ToArray();
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
You can decorate your class with JsonConverter attribute to use the Custom Json Converter.
class Wrapper
{
public Foo Foo{get;set;}
}
class Foo
{
[JsonProperty("Name")]
public string Name{get;set;}
[JsonProperty("versions")]
[JsonConverter(typeof(CustomConverter<Version>))]
public Version[] versions{get;set;}
}
Now you can deserialize as
var result =JsonConvert.DeserializeObject<Wrapper>(str);
This would now work, if you use either of following Json definition.
{
'Foo':
{
'Name': 'test',
'versions': [
{
'Major': '1',
'Minor': '1'
},
{
'Major': '2',
'Minor': '1'
},
{
'Major': '3',
'Minor': '1'
}]
}
}
Single Version (Without Array syntax)
{
'Foo':
{
'Name': 'test',
'versions':
{
'Major': '1',
'Minor': '1'
}
}
}

Related

JSON response with varying nesting pattern from 3rd-party API

I get a third-party API response which has several nested properties:
Variation 1:
{
"Property1": {
"Property2A": {
"Key1": "1",
"Key2": "2",
"Key3": "3"
},
"Property2B": {
"Property3": {
"Key4": "A",
"Key5": "B",
"Key6": "C",
"Property4": {
"Property5": {
"Property6": [
{
"Key7": "1",
"Property7A": {
"X": "1"
},
"Property7B": {
"X": "2"
},
"Property7C": {
"X": "3"
}
},
{
"Property7D": {
"X": "INeedThisString"
}
}
]
}
}
}
}
}
}
I only need the value "INeedThisString".
I am able to reach the value of property "X": "INeedThisString" by a suitable model structure (generated by mapping the model over the Json-File) and with the following declarations:
Rootobject obj = JsonConvert.DeserializeObject<Rootobject>(MyJsonString);
string result = obj.Property1.Property2B.Property3.Property4.Property5.Property6[1].Property7D.X;
Here's my problem:
The API sometimes issues a variation of this architecture which has -as the only difference- Property3 declared as an array such as:
Variation 2:
{
"Property1": {
"Property2A": {
"Key1": "1",
"Key2": "2",
"Key3": "3"
},
"Property2B": {
"Property3": [ //<-----
{
"Key4": "A",
"Key5": "B",
"Key6": "C",
"Property4": {
"Property5": {
"Property6": [
{
"Key7": "1",
"Property7A": {
"X": "1"
},
"Property7B": {
"X": "2"
},
"Property7C": {
"X": "3"
}
},
{
"Property7D": {
"X": "INeedThisString"
}
}
]
}
}
}
] //<-----
}
}
}
//<-----: added 2x for illustration purposes.
Obviously, variation 1 and my current model structure do not declare Property3 as an array.
Question:
What's an elegant approach to solve this without touching the Json (deletions/replacements) in pre-processing?
Should I implement an alternative set of models and switch between those two model-sets via an error-function? Please note that the keys within properties 7A-7D are all the same: "X".
My current working solution covers an alternative set of classes needed for parsing, resulting from mapping Variant 2 of the Json response.
All classes are decorated by Json headers [JsonProperty("actual-string-shown-in-Json")] via using directive using Newtonsoft.Json.
The two alternative JsonConvert functions are placed within a try-catch statement:
string result;
try
{
Rootobject obj = JsonConvert.DeserializeObject<Rootobject>(MyJsonString);
result = obj.Property1.Property2B.Property3.Property4.Property5.Property6[1].Property7D.X;
}
catch
{
AltRootobject obj = JsonConvert.DeserializeObject<AltRootobject>(MyJsonString);
result = obj.AltProperty1.AltProperty.AltProperty[0].AltProperty.AltProperty5.AltProperty6[1].AltProperty7D.AltX;
}

Spring Cloud Contract generates Pacts with empty body when the body is a list

I'm trying to generate pacts from spring cloud contracts as shown at the documentation. It works just find when the response body root is a json, however when I'm trying to generate a pact that return an array of jsons it generates an empty body. I´ve tried using groovy dsl with String format """[{}...]""" and using DslProperty [value()...]. Here are my contracts:
With string format
Contract.make {
description "should return a list of dummy object with dummy value. Generates pact with empty json"
request {
method GET()
url("/dummy")
}
response {
body("""[{"value": "Hi! I'm a dummy object ;)"}]""")
headers {
contentType applicationJson()
}
status 200
}}
With DslProperty
Contract.make {
description "should return a list of dummy object with dummy value. Generates pact with empty body list"
request {
method GET()
url("/dummy")
}
response {
body([value(value: "Hi! I'm a dummy object ;)")])
headers {
contentType applicationJson()
}
status 200
}}
And that's the file generate at target/pacts
{
"provider": {
"name": "Provider"
},
"consumer": {
"name": "Consumer"
},
"interactions": [
{
"description": "should return a list of dummy object with dummy value. Generates pact with empty body list",
"request": {
"method": "GET",
"path": "/dummy"
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"body": [
],
"matchingRules": {
"header": {
"Content-Type": {
"matchers": [
{
"match": "regex",
"regex": "application/json.*"
}
],
"combine": "AND"
}
}
}
}
},
{
"description": "should return a list of dummy object with dummy value. Generates pact with empty json",
"request": {
"method": "GET",
"path": "/dummy"
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"body": {
},
"matchingRules": {
"header": {
"Content-Type": {
"matchers": [
{
"match": "regex",
"regex": "application/json.*"
}
],
"combine": "AND"
}
}
}
}
}
],
"metadata": {
"pactSpecification": {
"version": "3.0.0"
},
"pact-jvm": {
"version": "3.5.23"
}
}}
I'm using the following versions
<spring-cloud.version>Hoxton.BUILD-SNAPSHOT</spring-cloud.version>
<spring-cloud-contract.version>2.0.1.RELEASE</spring-cloud-contract.version>
<pact-jvm-provider-maven.version>3.5.23</pact-jvm-provider-maven.version>
and that's my plugin configuration
<!-- SCC to pact see https://cloud.spring.io/spring-cloud-contract/reference/html/howto.html#how-to-generate-pact-from-scc-->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.6.0</version>
<executions>
<execution>
<id>convert-dsl-to-pact</id>
<phase>process-test-classes</phase>
<configuration>
<classpathScope>test</classpathScope>
<mainClass>
org.springframework.cloud.contract.verifier.util.ToFileContractsTransformer
</mainClass>
<arguments>
<argument>
org.springframework.cloud.contract.verifier.spec.pact.PactContractConverter
</argument>
<argument>${project.basedir}/target/pacts</argument>
<argument>
${project.basedir}/src/test/resources/contracts
</argument>
</arguments>
</configuration>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
</plugin>
While debugging throgh the plugin I've seen that what is happening respectively is:
- When declaring body as “”” [{...}] “”” Pact converter assumes that the body is an String instance so it goes through traverse method at org.springframework.cloud.contract.verifier.spec.pact.BodyConverter. And since it starts with [ it is not parsed.
org.springframework.cloud.contract.verifier.spec.pact.BodyConverter
private static DslPart traverse(Object value, DslPart parent, Closure dslPropertyValueExtractor) {
...
if (v instanceof String) {
v = v.trim()
if (v.startsWith("{") && v.endsWith("}")) {
try {
v = jsonSlurper.parseText(v as String)
}
catch (JsonException ex) { /*it wasn't a JSON string after all...*/
}
}
}
...
On the other hand, when going through the plugin code using the DslProperty I have an object like [DslProperty{clientValue=DslProperty}]. The first DslProperty is being extracted but since the content is another DslProperty and there’s no recursive extraction I end up with an empty body because v isn’t an instance of the Gstring, String, Number, Map, Collection. So I get an empty body again.
org.springframework.cloud.contract.verifier.spec.pact.BodyConverter
private static void processCollection(Collection values, PactDslJsonArray jsonArray, Closure dslPropertyValueExtractor) {
values.forEach({
Object v = it
if (v instanceof DslProperty) {
v = dslPropertyValueExtractor(v)
}
if (v instanceof GString) {
v = ContentUtils.extractValue(v, dslPropertyValueExtractor)
}
if (v == null) {
jsonArray.nullValue()
}
else if (v instanceof String) {
jsonArray.string(v)
}
else if (v instanceof Number) {
jsonArray.number(v)
}
else if (v instanceof Map) {
PactDslJsonBody current = jsonArray.object()
traverse(v, current, dslPropertyValueExtractor)
current.closeObject()
}
else if (v instanceof Collection) {
PactDslJsonArray current = jsonArray.array()
traverse(v, current, dslPropertyValueExtractor)
current.closeArray()
}
})
}
I've published an example at https://github.com/brjt23/contract-to-pact/tree/master in case more info about how I did build the project is required.
Is there something I'm doing wrong on defining my groovy contract files? I guess I missunderstood something about how the response body should be defined.
You need to create an array of groovy objects in your body like this:
body([
[value: "Object1"],
[value: "Object2"]
])
This way spring cloud contracts will generate the correct code needed for your contracts.

Spatial Point data type is not serialized correctly when using custom System.Text.Json serializer for Cosmos .NET v3 SDK

I want to use the System.Text.Json Json serializer with the Cosmos .NET v3 SDK.
So I use:
var client = new CosmosClientBuilder
(accountEndpoint: "https://localhost:8081", "<key>")
.WithCustomSerializer(new SystemTextJsonCosmosSerializer(new JsonSerializerOptions {}))
.Build();
With the custom serializer being (taken from Github issue):
public class SystemTextJsonCosmosSerializer : CosmosSerializer
{
private readonly JsonSerializerOptions _options;
public SystemTextJsonCosmosSerializer(JsonSerializerOptions options)
{
_options = options;
}
/// <inheritdoc />
public override T FromStream<T>(Stream stream)
{
// Have to dispose of the stream, otherwise the Cosmos SDK throws.
// https://github.com/Azure/azure-cosmos-dotnet-v3/blob/0843cae3c252dd49aa8e392623d7eaaed7eb712b/Microsoft.Azure.Cosmos/src/Serializer/CosmosJsonSerializerWrapper.cs#L22
// https://github.com/Azure/azure-cosmos-dotnet-v3/blob/0843cae3c252dd49aa8e392623d7eaaed7eb712b/Microsoft.Azure.Cosmos/src/Serializer/CosmosJsonDotNetSerializer.cs#L73
using (stream)
{
// TODO Would be more efficient if CosmosSerializer supported async
using var memory = new MemoryStream((int)stream.Length);
stream.CopyTo(memory);
byte[] utf8Json = memory.ToArray();
return JsonSerializer.Deserialize<T>(utf8Json, _options);
}
}
/// <inheritdoc />
public override Stream ToStream<T>(T input)
{
byte[] utf8Json = JsonSerializer.SerializeToUtf8Bytes(input, _options);
return new MemoryStream(utf8Json);
}
}
However, the Point data type is then not properly serilized.
public Point Location { get; set; }
It should be (as with Cosmos .NET SDK v3/Newtonsoft , v4 Preview/System.Text.Json) be:
"location": {
"type": "Point",
"coordinates": [
8.0000,
47.0000
]
},
But instead it ends up being:
"location": {
"Position": {
"Coordinates": [
8.0000,
47.0000
],
"Longitude": 8.0000,
"Latitude": 47.0000,
"Altitude": null
},
"Crs": {
"Type": 0
},
"Type": 0,
"BoundingBox": null,
"AdditionalProperties": {}
},
Anyone has idea on why, and how I can make it serialize properly?

DynamoDB Set of Objects Insert and Get

I have the following DynamoDB Table
#Builder
#DynamoDBTable(tableName = "rule_templates")
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class DynamoRuleTemplate {
private String id;
private DataType dataType;
private String displayName;
private String helpText;
private String expression;
private Set<Param> params;
#DynamoDBHashKey(attributeName = "id")
public String getId() {
return id;
}
#DynamoDBAttribute(attributeName = "data_type")
public DataType getDataType() {
return dataType;
}
#DynamoDBAttribute(attributeName = "display_name")
public String getDisplayName() {
return displayName;
}
#DynamoDBAttribute(attributeName = "help_text")
public String getHelpText() {
return helpText;
}
#DynamoDBAttribute(attributeName = "expression")
public String getExpression() {
return expression;
}
#DynamoDBTypeConverted(converter = ParamSet.class)
#DynamoDBAttribute(attributeName = "params")
public Set<Param> getParams() {
return params;
}
}
Here is the Param class:
#Data
public class Param {
private DataType dataType;
private String identifier;
private String displayName;
private String helpText;
}
I am pre-filling the table with some data in the form of json through AWS CLI like:
{
"id": {
"S": "string-length-range"
},
"data_type": {
"S": "STRING"
},
"display_name": {
"S": "String length range"
},
"expression": {
"S": "$val.length() <= $max && $val.length() >= $min"
},
"help_text": {
"S": "String length range"
},
"params": {
"L": [
{
"M": {
"dataType": {
"S": "INTEGER"
},
"displayName": {
"S": "max"
},
"identifier": {
"S": "$max"
}
}
},
{
"M": {
"dataType": {
"S": "INTEGER"
},
"displayName": {
"S": "min"
},
"identifier": {
"S": "$min"
}
}
}
]
}
}
But while doing a GET from the table using JAVA application, I'm getting the following error for params field: com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMappingException: expected S in value {L: [{M: {identifier={S: $max,}, displayName={S: max,}, dataType={S: INTEGER,}},}, {M: {identifier={S: $min,}, displayName={S: min,}, dataType={S: INTEGER,}, rule={M: {expression={S: $min >= 0,}},}},}],}
Can someone help me with the correct json so that I don't get the above error?

Json Structure Issue

A third party API is returning JSON in the following format
{
"group1": {
"Colour": "Blue",
"Name": "Dave"
},
"group2": {
"Colour": "Red",
"Name": "Karen"
},
"group3": {
"Colour": "Green",
"Name": "Ryan"
}
}
I'm finding the outer 'groupX' identifier to problematic when attempting to deserialize the JSON using JSON.NET.
Does anyone know best to parse JSON in this format?
{
"employees": [
{ "first-name":"John" , "last-name":"Doe" },
{ "first-name":"Anna" , "last-name":"Smith" },
{ "first-name":"Peter" , "last-name":"Jones" }
]
}
Your JSON is Correct.
if tou want to check than use below url JSONlint
Define a class Group like this:
class Group
{
public string Colour { get; set; }
public string Name { get; set; }
}
Then you can deserialize like this:
var dict = JsonConvert.DeserializeObject<Dictionary<string, Group>>(json);
Here's a quick demo:
string json = #"
{
""group1"": {
""Colour"": ""Blue"",
""Name"": ""Dave""
},
""group2"": {
""Colour"": ""Red"",
""Name"": ""Karen""
},
""group3"": {
""Colour"": ""Green"",
""Name"": ""Ryan""
}
}";
var dict = JsonConvert.DeserializeObject<Dictionary<string, Group>>(json);
foreach (var kvp in dict)
{
Console.WriteLine(kvp.Key);
Group group = kvp.Value;
Console.WriteLine(" Colour: " + group.Colour);
Console.WriteLine(" Name: " + group.Name);
}
Output:
group1
Colour: Blue
Name: Dave
group2
Colour: Red
Name: Karen
group3
Colour: Green
Name: Ryan

Resources