I have data that looks like this:
{
"status": "success",
"data": {
"irrelevant": {
"serialNumber": "XYZ",
"version": "4.6"
},
"data": {
"lib": {
"files": [
"data1",
"data2",
"data3",
"data4"
],
"another file": [
"file.jar",
"lib.jar"
],
"dirs": []
},
"jvm": {
"maxHeap": 10,
"maxPermSize": "12"
},
"serverId": "134",
"version": "2.3"
}
}
}
Here is the function I'm using to prettify the JSON data:
public static String stringify(Object o, int space) {
ObjectMapper mapper = new ObjectMapper();
try {
return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(o);
} catch (Exception e) {
return null;
}
}
I am using the Jackson JSON Processor to format JSON data into a String.
For some reason the JSON format is not in the format that I need. When passing the data to that function, the format I'm getting is this:
{
"status": "success",
"data": {
"irrelevant": {
"serialNumber": "XYZ",
"version": "4.6"
},
"another data": {
"lib": {
"files": [ "data1", "data2", "data3", "data4" ],
"another file": [ "file.jar", "lib.jar" ],
"dirs": []
},
"jvm": {
"maxHeap": 10,
"maxPermSize": "12"
},
"serverId": "134",
"version": "2.3"
}
}
}
As you can see under the "another data" object, the arrays are displayed as one whole line instead of a new line for each item in the array. I'm not sure how to modify my stringify function for it to format the JSON data correctly.
You should check how DefaultPrettyPrinter looks like. Really interesting in this class is the _arrayIndenter property. The default value for this property is FixedSpaceIndenter class. You should change it with Lf2SpacesIndenter class.
Your method should looks like this:
public static String stringify(Object o) {
try {
ObjectMapper mapper = new ObjectMapper();
DefaultPrettyPrinter printer = new DefaultPrettyPrinter();
printer.indentArraysWith(new Lf2SpacesIndenter());
return mapper.writer(printer).writeValueAsString(o);
} catch (Exception e) {
return null;
}
}
I don't have enough reputation to add the comment, but referring to the above answer Lf2SpacesIndenter is removed from the newer Jackson's API (2.7 and up), so instead use:
printer.indentArraysWith(DefaultIndenter.SYSTEM_LINEFEED_INSTANCE);
Source of the solution
Related
I have data saved into Realtime Firebase as an iterative JSON as shown in the picture.
Realtime Firebase data
[
{
"name": "Math",
"subMenu": [
{
"name": "Math1",
"subMenu": [
{
"name": "Math 1.1"
},
{
"name": "Math 1.2",
"subMenu": [
{
"name": "Math 1.2.1",
"subMenu": [
{
"name": "Math 1.2.1.1"
},
{
"name": "Math 1.2.1.2"
}
]
},
{
"name": "Math 1.2.2"
}
]
}
]
},
{
"name": "Math2"
},
{
"name": "Math3",
"subMenu": [
{
"name": "Math 1.3.1"
},
{
"name": "Math 1.3.2"
}
]
}
]
},
{
"name": "Marketing",
"subMenu": [
{
"name": "Promotions",
"subMenu": [
{
"name": "Catalog Price Rule"
},
{
"name": "Cart Price Rules"
}
]
},
{
"name": "Communications",
"subMenu": [
{
"name": "Newsletter Subscribers"
}
]
}
]
}
]
How the JSON look like in Realtime Firebase
'Click the image'
datamodel.dart
class Menu {
String? name;
int? font;
List<Menu>? subMenu = [];
Menu({this.name, this.subMenu, this.font});
Menu.fromJson(Map<String, dynamic> json) {
font = json['font'];
name = json['name'];
if (json['subMenu'] != null) {
json['subMenu'].forEach((v) {
subMenu?.add(Menu.fromJson(v));
});
}
}
}
My goal is to build a multilevel list view in Flutter that reflexes iterative JSON structure. So, I implemented a method that returns List<Menu>, and then pass it to a Futurebuilder to build a multilevel list View.
The method.
final ref = FirebaseDatabase.instance.ref();
Future<List<Menu>> firebaseCalls(DatabaseReference ref) async {
final snapshot = await ref.child('Task').get();
final jsondata = snapshot.value as Map<String, dynamic>;
final list = json.decode(jsondata) as List<dynamic>; // Error Location
return list.map((e) => Menu.fromJson(e)).toList();
}
and I got the following
The Error
error: The argument type 'Map<String, dynamic>' can't be assigned to the parameter type 'String'. (argument_type_not_assignable at [flutter_multilevel_list_from_json] lib\main.dart:28)
tried to change the list type to List<dynamic> but still give me an error.
json.decode() takes a String as input, and you are passing a Map<String,dynamic> into it.
That is your problem, not that you are trying to cast it to a List<dynamic>
May be this will be helpful (jsondata as List).map((e) => Menu.fromJson(e)).toList();
I have been struggling with converting the below json .
Is the data class should be mapped as Map<String,Object>.Then use Object mapper class to all values?What is the best way to map below pojo
Json Response
{
"data": {
"Order": [
{
{
"Property1" : Number1
"Propery2": Number 2
},
"Lines": [
{
"Id": "123456",
"itemDescription": "Green glass",
}
],
"date": "",
"updateDate": ""
}
]
}
}
Data class:
#JsonIgnoreProperties(ignoreUnknown = true)
public class Details {
public Map<String,Object> data;
}
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.
I'm using REST-Assured to test some API. My API clearly respond with a JSON and according to the doc if this is the response:
{
"id": "390",
"data": {
"leagueId": 35,
"homeTeam": "Norway",
"visitingTeam": "England",
},
"odds": [{
"price": "1.30",
"name": "1"
},
{
"price": "5.25",
"name": "X"
}]
}
I could test like this:
#Test
public void givenUrl_whenSuccessOnGetsResponseAndJsonHasRequiredKV_thenCorrect() {
get("/events?id=390")
.then()
.statusCode(200)
.assertThat()
.body("data.leagueId", equalTo(35));
}
Surely this is readable but I would a full comparison of the JSON (i.e.: this is the JSON response; this is a canned JSON - a resource file would be perfect - are those JSON equals?). Does REST-Assured offer something like that or I need to make it manually.
Use RestAssured's JsonPath to parse the json file into a Map and then compare it with Hamcrest Matchers. This way the order etc didn't matter.
import static org.hamcrest.Matchers.equalTo;
import io.restassured.path.json.JsonPath;
...
JsonPath expectedJson = new JsonPath(new File("/path/to/expected.json"));
given()
...
.then()
.body("", equalTo(expectedJson.getMap("")));
Karate is exactly what you are looking for - you can perform a full equality match of a JSON payload in one step.
And for the cases where you have dynamic values (generated keys, timestamps) Karate provides a very elegant way for you to ignore (or just validate the format of) these keys.
One the primary motivations for creating Karate was to come up with a better alternative to REST-assured. You can refer to this document which can help you evaluate Karate and make a case for it in your organization: Karate vs REST-assured.
REST Assured does not support JSON comparison, only schema and parts of the body as you have in your question. What you can do is using Hamcrest's JSON comparitorSameJSONAs in Hamcrest JSON SameJSONAs
If somebody is looking for method without parsing json-file.
You can check the body size at the beginning using Matchers.aMapWithSize(size), and then check the contents as usual.
Example:
#Test
public void getAccount_forbidden_whenUserIsAnonymous() {
RestAssured
.get("/account")
.then()
.statusCode(HttpServletResponse.SC_FORBIDDEN)
.body("", Matchers.aMapWithSize(5),
"timestamp", Matchers.notNullValue(),
"status", Matchers.equalTo(HttpServletResponse.SC_FORBIDDEN),
"error", Matchers.equalTo("Forbidden"),
"message", Matchers.equalTo("Access Denied"),
"path", Matchers.equalTo("/account"));
}
You can use Validate with JSON SCHEMA in RestAssured.
Try this code:
// Base Test [BaseTest.java]
public class BaseTest {
protected RequestSpecification requestSpecificationToMerge = new RequestSpecBuilder()
.setBaseUri("BASE URL")
.setContentType(ContentType.JSON)
.build();
#BeforeMethod
public void setFilter() {
RestAssured.filters(new AllureRestAssured());
}
}
// Test [Home.java]
public class Home extends BaseTest {
#Test(priority = 0)
public void getHome() {
given()
.spec(requestSpecificationToMerge)
.basePath("/your endpoint")
.when()
.get()
.then()
.log().body()
.body(matchesJsonSchemaInClasspath("home.json"));
}
// JSON SCHEMA [home.json]
{
"type": "object",
"required": [
"data",
"meta",
"status"
],
"properties": {
"data": {
"type": ["array", "null"],
"items": {
"type": "object",
"required": [
"id",
"title",
"sorting"
],
"properties": {
"id": {
"type": "integer"
},
"title": {
"type": "string"
},
"sorting": {
"type": "integer"
}
}
}
},
"meta": {
"type": ["object", "null"],
"required": [
"pagination"
],
"items": {
"type": "object",
"required": [
"current_page",
"per_page",
"total",
"total_page"
],
"properties": {
"current_page": {
"type": "integer"
},
"per_page": {
"type": "integer"
},
"total": {
"type": "integer"
},
"total_page": {
"type": "integer"
}
}
}
},
"status": {
"type": "object",
"required": [
"code",
"message_client",
"message_server"
],
"properties": {
"code": {
"type": "integer",
"enum": [
200,
404
]
},
"message_client": {
"type": "string"
},
"message_server": {
"type": "string"
}
}
}
}
}
Easy way:
String s = "{\"ip\": \"your_ip\"}";
given().log().all().contentType(ContentType.JSON).get("http://ip.jsontest.com/").then().log().all().assertThat().body(containsString(s))
Not easy way: you can create custom matcher or use jsonpath, it has options to comapre jsons.
Apparently, rest-assured only provides capabilities to validate the schema as described here.
However, it's quite simple to make an exact comparison using jackson-databind and junit.
We should write a function that compares a body returned by rest-assured with a file in the resources directory
import org.junit.Assert;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
void assertJsonEquals(String expectedJson, ResponseBodyExtractionOptions actualBody) throws IOException {
Assert.assertNotNull("The request returned no body", expectedJson);
final ObjectMapper mapper = new ObjectMapper();
Assert.assertEquals(
mapper.readTree(Objects.requireNonNull(getClass().getClassLoader().getResource(expectedJsonPath)).openStream().readAllBytes()),
mapper.readTree(body.asByteArray())
);
}
Then, use it as shown below
final ResponseBodyExtractionOptions actualBody = given()
.accept("application/json")
.contentType(MediaType.APPLICATION_JSON)
.when()
.get("...")
.then()
.extract().body();
assertJsonEquals("expected-payload.json", actualBody);
You can make use of JSONAssert Library to match the entire JSON Response. I recently wrote a blog on how to achieve it.
Below is the basic usage on how to use the library:
JSONAssert.assertEquals(expectedResponse, actualResponse, JSONCompareMode.LENIENT);
I have a JSON consisting of the following data:
{
"comment": {
"S": "Nice"
},
"id": {
"S": "1ca38300-8938-11e5-8656-9bf2d3249757"
},
"postId": {
"S": "083c1f50-8b84-11e5-9021-7da869825160"
},
"spam": {
"N": "0"
},
"tags": {
"L": [
{
"S": "test1"
},
{
"S": "test2"
}
]
}
}
And now, I want to format the above JSON to the following format, like:
{
"comment":"Nice",
"id":"1ca38300-8938-11e5-8656-9bf2d3249757",
"postId":"083c1f50-8b84-11e5-9021-7da869825160",
"tags":["test1","test2"]
}
Load your json and manipulate like a plain object and then save it.
Here is how to transform your object:
var newObj = {
comment: oldObj.comment.S,
id: oldObj.id.S,
postId: oldObj.postId.S,
tags: oldObj.tags.L.map(function (entry) {
return entry.S;
})
}
Hey find the below code
suppose ur above jsonObject is completely names are and placed under result Object.
JSONObject obj = new JSONObject();
obj.put("comment",result.getJSONObject("comment").getString("s"));
obj.put("id",result.getJSONObject("id").getString("s"));
obj.put("postId",result.getJSONObject("postId").getString("s"));
List<String> test = new ArrayList<String>;
JSONArray js = result.getJSONObject("tags").getJSONArray("L");
for(I=o;i<js.length();I++){
test.add(js.get(i).getString("s"));
}
obj.put("tags",test);