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
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;
}
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.
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?
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?
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