I've been struggling with this for long, I believe there's something wrong with my record design.
MY json looks like below. I posted this earlier as a part of another question but didn't get full answer especially for this part. Each record within the array is an object with a field name with data of type string.
JSON
[
{
"data": "/publication/a40a5e5c-98b3-45ae-d6a3-924b31d8712a/article/test;version=1521246543034"
},
{
"data": "/publication/a40a5e5c-98b3-45ae-d6a3-924b31d8712a/book/test2;version=1520623346891"
},
{
"data": "/publication/a40a5e5c-98b3-45ae-d6a3-924b31d8712a/catalog/test3;version=1520623346833"
}
]
List.fs
open System.Runtime.Serialization
[<DataContract>]
type List= {
[<field: DataMemberAttribute(Name="data") >]
Data: string
}
Parsing JSON
let response = request.GetResponse() :?> HttpWebResponse
use reader = new StreamReader(response.GetResponseStream())
use memoryStream = new MemoryStream(ASCIIEncoding.Default.GetBytes(reader.ReadToEnd()))
let jsonSerializer = DataContractJsonSerializer(typeof<List[]>)
let result = jsonSerializer.ReadObject(memoryStream) :?> List[]
Debug.WriteLine(sprintf "%A" result)
Actual Output - having nulls
[|
{Data = null;};
{Data = null;};
{Data = null;}
|]
Expected Output
[|
{Data = "/publication/a40a5e5c-98b3-45ae-d6a3-924b31d8712a/article/test;version=1521246543034";};
{Data = "/publication/a40a5e5c-98b3-45ae-d6a3-924b31d8712a/book/test2;version=1520623346891";}
{Data = "/publication/a40a5e5c-98b3-45ae-d6a3-924b31d8712a/catalog/test3;version=1520623346833";}
|]
Iteration
result
> Array.iter (fun x -> Console.WriteLine(x.Href))
I suspect there must be something wrong with how you're reading the data. I tried to reproduce this and replaced the reading from a stream with reading from a string - so that I can test this - and the following works fine (in F# interactive version 14.0.23413.0):
#r "System.Runtime.Serialization"
open System.IO
open System.Text
open System.Runtime.Serialization
open System.Runtime.Serialization.Json
[<DataContract>]
type List= {
[<field: DataMemberAttribute(Name="data") >]
Data: string
}
let reader = new StringReader("""[
{ "data": "/publication/a40a5e5c/article/test;version=1521246543034" },
{ "data": "/publication/a40a5e5c/book/test2;version=1520623346891" },
{ "data": "/publication/a40a5e5c/catalog/test3;version=1520623346833" } ]""")
let memoryStream = new MemoryStream(ASCIIEncoding.Default.GetBytes(reader.ReadToEnd()))
let jsonSerializer = DataContractJsonSerializer(typeof<List[]>)
let result = jsonSerializer.ReadObject(memoryStream) :?> List[]
result
Can you check that your input JSON is actually the one you shared here? To do that, see what reader.ReadToEnd() returns before calling GetBytes - I suspect there must be something wrong there, because the rest of the code works fine for me.
Related
I am very new to this as you can probably tell, but i'm trying to parse a JSON url with Volley using Kotlin in Android Studio. The url contains nested Objects, not nested Arrays.
I can display everything inside "questionnaire", but I only want to display "typeOfQuestion". How do i do that?
MainActivity.kt:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
questionTV = findViewById(R.id.idTVQuestion)
answerTV = findViewById(R.id.idTVAnswer)
typeTV = findViewById(R.id.idTVType)
val queue: RequestQueue = Volley.newRequestQueue(applicationContext)
val request = JsonObjectRequest(Request.Method.GET, url, null, { response ->
loadingPB.setVisibility(View.GONE)
try {
val question: String = response.getString("question")
val answer: String = response.getString("answer")
val typeOfQuestion: String = response.getString("typeOfQuestion")
questionTV.text = question
answerTV.text = answer
typeTV.text = typeOfQuestion
} catch (e: Exception) {
e.printStackTrace()
}
}, { error ->
Log.e("TAG", "RESPONSE IS $error")
Toast.makeText(this#MainActivity, "Fail to get response", Toast.LENGTH_SHORT)
.show()
})
queue.add(request)
}
}
Heres the JSON:
{
"questionnaire": {
"question": "Where do you live?",
"answer": "In the mountains",
"typeOfQuestion": "Informative
}
}
You have object inside another json object.If you need to access field from child object you need to get child jsonObject and then get fields from object.
var questionnaire = response.getJSONObject("questionnaire")
You need to get fields from questionnaire object.Like.
val question: String = questionnaire.getString("question")
val answer: String = questionnaire.getString("answer")
val typeOfQuestion: String = questionnaire.getString("typeOfQuestion")
The problem I am trying to solve is perfectly described by the following text got from this link:
For a concrete example of when this could be useful, consider an API that supports partial updates of objects. Using this API, a JSON object would be used to communicate a patch for some long-lived object. Any included property specifies that the corresponding value of the object should be updated, while the values for any omitted properties should remain unchanged. If any of the object’s properties are nullable, then a value of null being sent for a property is fundamentally different than a property that is missing, so these cases must be distinguished.
That post presents a solution but using the kotlinx.serialization library, however, I must use gson library for now.
So I am trying to implement my own solution as I didn't find anything that could suit my use case (please let me know if there is).
data class MyObject(
val fieldOne: OptionalProperty<String> = OptionalProperty.NotPresent,
val fieldTwo: OptionalProperty<String?> = OptionalProperty.NotPresent,
val fieldThree: OptionalProperty<Int> = OptionalProperty.NotPresent
)
fun main() {
val gson = GsonBuilder()
.registerTypeHierarchyAdapter(OptionalProperty::class.java, OptionalPropertyDeserializer())
.create()
val json1 = """{
"fieldOne": "some string",
"fieldTwo": "another string",
"fieldThree": 18
}
"""
println("json1 result object: ${gson.fromJson(json1, MyObject::class.java)}")
val json2 = """{
"fieldOne": "some string",
"fieldThree": 18
}
"""
println("json2 result object: ${gson.fromJson(json2, MyObject::class.java)}")
val json3 = """{
"fieldOne": "some string",
"fieldTwo": null,
"fieldThree": 18
}
"""
println("json3 result object: ${gson.fromJson(json3, MyObject::class.java)}")
}
sealed class OptionalProperty<out T> {
object NotPresent : OptionalProperty<Nothing>()
data class Present<T>(val value: T) : OptionalProperty<T>()
}
class OptionalPropertyDeserializer : JsonDeserializer<OptionalProperty<*>> {
private val gson: Gson = Gson()
override fun deserialize(
json: JsonElement?,
typeOfT: Type?,
context: JsonDeserializationContext?
): OptionalProperty<*> {
println("Inside OptionalPropertyDeserializer.deserialize json:$json")
return when {
// Is it a JsonObject? Bingo!
json?.isJsonObject == true ||
json?.isJsonPrimitive == true-> {
// Let's try to extract the type in order
// to deserialize this object
val parameterizedType = typeOfT as ParameterizedType
// Returns an Present with the value deserialized
return OptionalProperty.Present(
context?.deserialize<Any>(
json,
parameterizedType.actualTypeArguments[0]
)!!
)
}
// Wow, is it an array of objects?
json?.isJsonArray == true -> {
// First, let's try to get the array type
val parameterizedType = typeOfT as ParameterizedType
// check if the array contains a generic type too,
// for example, List<Result<T, E>>
if (parameterizedType.actualTypeArguments[0] is WildcardType) {
// In case of yes, let's try to get the type from the
// wildcard type (*)
val internalListType = (parameterizedType.actualTypeArguments[0] as WildcardType).upperBounds[0] as ParameterizedType
// Deserialize the array with the base type Any
// It will give us an array full of linkedTreeMaps (the json)
val arr = context?.deserialize<Any>(json, parameterizedType.actualTypeArguments[0]) as ArrayList<*>
// Iterate the array and
// this time, try to deserialize each member with the discovered
// wildcard type and create new array with these values
val result = arr.map { linkedTreeMap ->
val jsonElement = gson.toJsonTree(linkedTreeMap as LinkedTreeMap<*, *>).asJsonObject
return#map context.deserialize<Any>(jsonElement, internalListType.actualTypeArguments[0])
}
// Return the result inside the Ok state
return OptionalProperty.Present(result)
} else {
// Fortunately it is a simple list, like Array<String>
// Just get the type as with a JsonObject and return an Ok
return OptionalProperty.Present(
context?.deserialize<Any>(
json,
parameterizedType.actualTypeArguments[0]
)!!
)
}
}
// It is not a JsonObject or JsonArray
// Let's returns the default state NotPresent.
else -> OptionalProperty.NotPresent
}
}
}
I got most of the code for the custom deserializer from here.
This is the output when I run the main function:
Inside OptionalPropertyDeserializer.deserialize json:"some string"
Inside OptionalPropertyDeserializer.deserialize json:"another string"
Inside OptionalPropertyDeserializer.deserialize json:18
json1 result object: MyObject(fieldOne=Present(value=some string), fieldTwo=Present(value=another string), fieldThree=Present(value=18))
Inside OptionalPropertyDeserializer.deserialize json:"some string"
Inside OptionalPropertyDeserializer.deserialize json:18
json2 result object: MyObject(fieldOne=Present(value=some string), fieldTwo=my.package.OptionalProperty$NotPresent#573fd745, fieldThree=Present(value=18))
Inside OptionalPropertyDeserializer.deserialize json:"some string"
Inside OptionalPropertyDeserializer.deserialize json:18
json3 result object: MyObject(fieldOne=Present(value=some string), fieldTwo=null, fieldThree=Present(value=18))
I am testing the different options for the fieldTwo and it is almost fully working, with the exception of the 3rd json, where I would expect that fieldTwo should be fieldTwo=Present(value=null) instead of fieldTwo=null.
And I see that in this situation, the custom deserializer is not even called for fieldTwo.
Can anyone spot what I am missing here? Any tip would be very appreciated!
I ended giving up of gson and move to moshi.
I implemented this behavior based on the solution presented in this comment.
I've managed to extract data from a POST method in hyper using the following:
use hyper::service::{make_service_fn, service_fn};
use hyper::{Body, Method, Request, Response, Server};
use std::convert::Infallible;
use std::net::SocketAddr;
use tokio;
async fn handle(_req: Request<Body>) -> Result<Response<Body>, hyper::Error> {
match (_req.method(), _req.uri().path()) {
(&Method::GET, "/") => Ok(Response::new(Body::from("this is a get"))),
(&Method::POST, "/") => {
let byte_stream = hyper::body::to_bytes(_req).await?;
let _params = form_urlencoded::parse(&byte_stream)
.into_owned()
.collect::<HashMap<String, String>>();
However, the whole JSON body is just one key in the HashMap now. How do I split it up so I have a hashmap with multiple keys and values as opposed to one key that's the entire body?
[dependencies]
futures = "0.1"
hyper = "0.13"
pretty_env_logger = "0.3.1"
url = "2.1.1"
tokio = { version = "0.2", features = ["macros", "tcp"] }
bytes = "0.5"
There is a discrepancy between your description:
However, the whole JSON body
And your code:
let _params = form_urlencoded::parse(&byte_stream)
If your data is JSON then parse it as JSON, using the serde_json crate:
let _params: HashMap<String, String> = serde_json::from_slice(&byte_stream).unwrap();
In context of this tutorial on F# snippets http://www.fssnip.net/1l/title/Convert-an-object-to-json-and-json-to-object
Let's say a Person type
type Person = {
entityName: string;
entityType: string;
}
and the code to call the web service and convert into json.
let internal json<'t> (myObj:'t) =
use ms = new MemoryStream()
(new DataContractJsonSerializer(typeof<'t>)).WriteObject(ms, myObj)
Encoding.Default.GetString(ms.ToArray())
let internal unjson<'t> (jsonString:string) : 't =
use ms = new MemoryStream(ASCIIEncoding.Default.GetBytes(jsonString))
let obj = (new DataContractJsonSerializer(typeof<'t>)).ReadObject(ms)
obj :?> 't
let requestToken (): Token =
let url = "http://example.com"
let request = WebRequest.Create(url) :?> HttpWebRequest
request.Method <- "POST"
request.Accept <- "application/json;charset=UTF-8"
let response = request.GetResponse() :?> HttpWebResponse
use reader = new StreamReader(response.GetResponseStream())
let body = reader.ReadToEnd()
Console.WriteLine body // result OK
let result = unjson<Person> body
JSON
{
"entityName": "john doe",
"entityType": "client"
}
Error
System.Runtime.Serialization.SerializationException: The data contract type 'Person' cannot be deserialized because the required data members 'entityName#, entityType#' were not found.
if someone can add an example on how to call 'json' function passing result object to convert the object back into the JSON string
Is the Person type required to have all fields as per JSON schema or can I choose to leave out non-required fields?
This will serialize your record to JSON using the DataContractSerializer. You need to add some attributes for this to work.
#r "System.Runtime.Serialization"
open System.IO
open System.Runtime.Serialization.Json
open System.Runtime.Serialization
[<DataContract>]
[<CLIMutable>]
type Person = {
[<DataMember(Name="Name") >]
entityName: string
[<DataMember(Name="Type") >]
entityType: string
}
let person = {entityName = "ENTITY"; entityType ="TYPE"}
let toJson<'t> (myObj:'t) =
let fs = new FileStream(#"C:\tmp\test.json",FileMode.Create)
(new DataContractJsonSerializer(typeof<'t>)).WriteObject(fs,myObj)
toJson<Person> person
And this is the output I get in the test.json file:
{"Name":"ENTITY","Type":"TYPE"}
I have the following Groovy script (not a Grails app) that is returning a JSON-like, but it is not strictly valid JSON.
String baseURL = 'https://test.com'
File userFile = new File("./user.json")
def client = new HTTPBuilder(baseUrl)
client.headers['Content-Type'] = 'application/json'
client.request(GET, JSON) { req ->
requestContentType = JSON
headers.Accept = 'application/json'
response.success = { resp, json ->
userFile.append json.toString()
println JsonOutput.toJson(json.toString())
}
}
I am trying to create a JSON output file. I have tried using JsonOutput.prettyPrint and I looked at JsonBuilder, but that looks like I would have to build the JSON structure manually when Groovy should support the output. This is what I am getting back.
{AssetNumber=AssetNumber1, DeviceFriendlyName=FriendlyName1, PhoneNumber=17035551231, SerialNumber=SerialNumber1, Udid=Udid1, UserEmailAddress=user1#email.com, UserId=userId1, UserName=userName1}
As I said, this is JSON-like, but not strictly valid. I was expecting something like:
{"AssetNumber": "AssetNumber1", "DeviceFriendlyName": "FriendlyName1"....}
Any ideas?
It works perfectly fine (groovy v 2.3.6):
import groovy.json.*
def pretty = JsonOutput.prettyPrint(JsonOutput.toJson([1:2]))
assert pretty == """{
"1": 2
}"""
In this closure:
response.success = { resp, json ->
userFile.append json.toString()
println JsonOutput.toJson(json.toString())
}
You're getting an instance of Map under json variable. You do not need to turn it into a string. Instead use:
userFile.append JsonOutput.toJson(json)
println JsonOutput.toJson(json)