Escaping Haskell record's field - json

Question
Is there a way to create a record with a field called "data"?
data MyRecord =
MyRecord {
otherField :: String,
data :: String -- compilation error
}
Why do I need it?
I've been writing a wrapper around a JSON API using Aeson and the remote service decided to call one of the fields data.
{
pagination: {
..
},
data: [
{ .. },
{ .. },
]
}

Yes, you can name the field something else than data, like:
data MyRecord =
MyRecord {
otherField :: String,
recordData :: String
}
And then derive a ToJSON with a key modifier:
labelMapping :: String -> String
labelMapping "recordData" = "data"
labelMapping x = x
instance ToJSON MyRecord where
toJSON = genericToJSON defaultOptions {
fieldLabelModifier = labelMapping
}
instance FromJSON Coord where
parseJSON = genericParseJSON defaultOptions {
fieldLabelModifier = labelMapping
}

Related

How to convert structure to json in dhall?

How do I convert arbitrary structure into json?
let Prelude = ./include/Prelude.dhall
let JSON = Prelude.JSON
let Foo = { a: Natural, t: Text }
let foo = { a = 10, b = "foo" }
in (DO_MAGIC foo) : JSON.Type
I know there is toMap builtin function, but it expects a homogeneous record.
What I actually trying to do is to write OpenAPI specification in dhall. Most parts of it are simple and nice, but json schema that describe incoming data shape is recursive, which is hard in Dhall. What I want would be expressed in Haskell like following
data Schema
= SInteger { minimum :: Maybe Int, example :: Maybe Int }
| SString { format :: Maybe String, length :: Maybe Int }
| Object [(String, Schema, Bool)] -- (name, schema, required)
deriving (ToJSON)
Since it looked hard in Dhall, I decided that I would go this way:
data SInteger = SInteger { minimum :: Maybe Int, example :: Maybe Int }
data SString = SString { format :: Maybe String, length :: Maybe Int }
data Object = Object [(String, Schema, Bool)] -- (name, schema, required)
integer :: SInteger -> Schema
string :: SString -> Schema
object :: Object -> Schema
type Schema = JSON
but on this road I am stuck too. I am willing to sacrifice some of type rigidity for not patching dhall-json.
The basic idea is outlined in this guide:
How to translate recursive code to Dhall
… and here is how that looks in the context of your example:
let List/map = https://prelude.dhall-lang.org/v17.1.0/List/map.dhall
let JSON = https://prelude.dhall-lang.org/v17.1.0/JSON/Type
let JSON/render = https://prelude.dhall-lang.org/v17.1.0/JSON/render
let SInteger = { minimum : Optional Integer, example : Optional Integer }
let SString = { format : Optional Text, length : Optional Natural }
let SObject =
λ(Schema : Type) → List { name : Text, schema : Schema, required : Bool }
let Constructors =
λ(Schema : Type) →
{ Integer : SInteger → Schema
, String : SString → Schema
, Object : SObject Schema → Schema
}
let Schema
: Type
= ∀(Schema : Type) → ∀(schema : Constructors Schema) → Schema
let integer
: SInteger → Schema
= λ(x : SInteger) →
λ(Schema : Type) →
λ(schema : Constructors Schema) →
schema.Integer x
let string
: SString → Schema
= λ(x : SString) →
λ(Schema : Type) →
λ(schema : Constructors Schema) →
schema.String x
let object
: List { name : Text, schema : Schema, required : Bool } → Schema
= λ(x : SObject Schema) →
λ(Schema : Type) →
λ(schema : Constructors Schema) →
let Input = { name : Text, schema : Schema#1, required : Bool }
let Output = { name : Text, schema : Schema, required : Bool }
let adapt =
λ(y : Input) →
{ schema = y.schema Schema schema } ∧ y.{ name, required }
in schema.Object (List/map Input Output adapt x)
let toJSON
: Schema → JSON
= λ(schema : Schema) →
λ(JSON : Type) →
λ ( json
: { array : List JSON → JSON
, bool : Bool → JSON
, double : Double → JSON
, integer : Integer → JSON
, null : JSON
, object : List { mapKey : Text, mapValue : JSON } → JSON
, string : Text → JSON
}
) →
schema
JSON
{ Integer =
λ(x : SInteger) →
json.object
( toMap
{ minimum =
merge
{ None = json.null, Some = json.integer }
x.minimum
, example =
merge
{ None = json.null, Some = json.integer }
x.example
}
)
, String =
λ(x : SString) →
json.object
( toMap
{ format =
merge
{ None = json.null, Some = json.string }
x.format
, length =
merge
{ None = json.null
, Some =
λ(n : Natural) →
json.integer (Natural/toInteger n)
}
x.length
}
)
, Object =
λ(x : SObject JSON) →
let Input = { name : Text, schema : JSON, required : Bool }
let Output = { mapKey : Text, mapValue : JSON }
let adapt =
λ(y : Input) →
{ mapKey = y.name
, mapValue =
json.object
( toMap
{ schema = y.schema
, required = json.bool y.required
}
)
}
in json.object (List/map Input Output adapt x)
}
let example =
let input =
object
[ { name = "foo"
, required = True
, schema = string { format = None Text, length = Some 10 }
}
, { name = "bar"
, required = False
, schema = integer { minimum = Some +0, example = Some +10 }
}
]
let output =
''
{
"foo": {
"required": true,
"schema": {
"format": null,
"length": 10
}
},
"bar": {
"required": false,
"schema": {
"example": 10,
"minimum": 0
}
}
}
''
in assert : JSON/render (toJSON input) ≡ output
in { Schema, integer, string, object, toJSON }

kotlin handling variable return type of function

Suppose I've multiple DTOs, like:
data class ActionDetailDTO(
#JsonProperty("priority")
val priority: String,
#JsonProperty("reason")
val reason: String
)
data class IntroDTO(
#JsonProperty("name")
val name: String,
#JsonProperty("number")
val number: String
)
and I've a json of these dtos stored as strings,
when I parse them doing something like this:
fun parseStringBasedOnType(action: SomeDTOType) :Any{
val obj = when (action.actionType){
"CREATED" -> objectMapper.readValue(action.actionDetails, ActionDetailDTO::class.java)
"INTRO" -> objectMapper.readValue(action.actionDetails, IntroDTO::class.java)
else -> "hh"
}
return obj
}
so:
val nn = parseStringBasedOnType(SomeActionObject) //type: CREATED
if(nn.actionType == "CREATED"){
println(nn.reason)
}
This obviously does not work, how can this be handled?
You could either define a common interface with an actionType method or cast the values:
if(nn is ActionDetailDTO) {
println(nn.reason)
}
Or use when if you plan to do something for the other type too:
when(nn) {
is ActionDetailDTO -> println(nn.reason)
is IntroDTO -> println(nn.number)
}

AESON: Parse dynamic structure

I have a JSON structure like this
{
"tag1": 1,
"tag2": 7,
...
}
And I have a type like this
data TagResult { name :: String, numberOfDevicesTagged :: Int } deriving (Show, Eq)
newtype TagResultList = TagResultList { tags :: [TagResult] }
The tag names are of course fully dynamic and I don't know them at compile time.
I'd like to create an instance FromJSON to parse the JSON data but I just cannot make it compile.
How can I define parseJSON to make this happen?
You can use the fact that Object is an HasMap and extract the key at runtime. You can then write the FromJSON instance as follows -
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Data.Aeson
import qualified Data.Text as T
import qualified Data.HashMap.Lazy as HashMap
data TagResult = TagResult { name :: String
, numberOfDevicesTagged :: Int
} deriving (Show, Eq)
newtype TagResultList = TagResultList { tags :: [TagResult] } deriving Show
instance ToJSON TagResult where
toJSON (TagResult tag ntag) =
object [ T.pack tag .= ntag ]
instance ToJSON TagResultList where
toJSON (TagResultList tags) =
object [ "tagresults" .= toJSON tags ]
instance FromJSON TagResult where
parseJSON (Object v) =
let (k, _) = head (HashMap.toList v)
in TagResult (T.unpack k) <$> v .: k
parseJSON _ = fail "Invalid JSON type"
instance FromJSON TagResultList where
parseJSON (Object v) =
TagResultList <$> v .: "tagresults"
main :: IO ()
main = do
let tag1 = TagResult "tag1" 1
tag2 = TagResult "tag2" 7
taglist = TagResultList [tag1, tag2]
let encoded = encode taglist
decoded = decode encoded :: Maybe TagResultList
print decoded
The above program should print the tag result list.
Just (TagResultList {tags = [TagResult {name = "tag1", numberOfDevicesTagged = 1},TagResult {name = "tag2", numberOfDevicesTagged = 7}]})

ScalaJson implicit Write, found: Any required: play.api.libs.json.Json.JsValueWrapper

I am building a web app using Scala / Play Framework and Reactive Mongo and I want the models to be defined in the database instead of having them hardcoded.
To do so, I am writing a class EntityInstance taking a Sequence of FieldInstance :
case class EntityInstance(fields: Seq[FieldInstance])
I am trying to accept fields from any types and to convert them to Json : example
new FieldInstance("name", "John") | json: { "name": "John" }
new FieldInstance("age", 18) | json: { "age": 18 }
At the moment I am trying to accept Strings, Booleans and Integers and if the type is not supported I write some error :
new FieldInstance("profilePicture", new Picture("john.jpg") | json: { "profilePicture": "Unsupported type
I wrote a FieldInstance class taking a fieldName as a String and a value as any type. As soon as that class is instantiated I cast the value to a known type or to the String describing the error.
class FieldInstance(fieldNamec: String, valuec: Any) {
val fieldName = fieldNamec
val value = valuec match {
case v: Int => v
case v: String => v
case v: Boolean => v
case _ => "Unrecognized type"
}
}
object FieldInstance {
implicit val fieldInstanceWrites = new Writes[FieldInstance] {
def writes(fieldInstance: FieldInstance) = Json.obj(
fieldInstance.fieldName -> fieldInstance.value
)
}
}
I created a companion object with an implicit Write to json so I can call "Json.toJson()" on an instance of FieldInstance and get a json as described on my examples above.
I get an error : found: Any required: play.api.libs.json.Json.JsValueWrapper
I understand that it comes from the fact that my value is of type Any but I thought the cast would change that Any to String || Boolean || Int before hitting the Writer.
PS: Ignore the bad naming of the classes, I could not name EntityInstance and FieldInstance, Entity and Field because these as the classes I use to describe my models.
I found a fix to my problem :
The type matching that I was doing in the class should be done in the implicit Write !
class FieldInstance(fieldNamec: String, valuec: Any) {
val fieldName = fieldNamec
val value = valuec
override def toString(): String = "(" + fieldName + "," + value + ")";
}
object FieldInstance {
implicit val fieldInstanceWrites = new Writes[FieldInstance] {
def writes(fieldInstance: FieldInstance) =
fieldInstance.value match {
case v: Int => Json.obj(fieldInstance.fieldName -> v.asInstanceOf[Int])
case v: String => Json.obj(fieldInstance.fieldName -> v.asInstanceOf[String])
case v: Boolean => Json.obj(fieldInstance.fieldName -> v.asInstanceOf[Boolean])
case _ => Json.obj(fieldInstance.fieldName -> "Unsupported type")
}
}
}
This code now allows a user to create an EntityInstance with Fields of Any type :
val ei = new EntityInstance(Seq[FieldInstance](new FieldInstance("name", "George"), new FieldInstance("age", 25), new FieldInstance("married", true)))
println("-- TEST ENTITY INSTANCE TO JSON --")
println(Json.toJson(ei))
prints : {"entity":[{"name":"George"},{"age":25},{"married":true}]}
Here is my EntityInstance code if you are trying to test it :
case class EntityInstance(fields: Seq[FieldInstance])
object EntityInstance {
implicit val EntityInstanceWrites = new Writes[EntityInstance] {
def writes(entityInstance: EntityInstance) =
Json.obj("entity" -> entityInstance.fields)
}
}
It is returning a String, Int or Boolean but Json.obj is expecting the value parameter of type (String, JsValueWrapper)
def obj(fields: (String, JsValueWrapper)*): JsObject = JsObject(fields.map(f => (f._1, f._2.asInstanceOf[JsValueWrapperImpl].field)))
a quick fix could be to convert the matched value v with toJson provided the implicit Writes[T] for type T is available (which they are for String, Int and Boolean)
class FieldInstance(fieldNamec: String, valuec: Any) {
val fieldName = fieldNamec
val value = valuec match {
case v: Int => Json.toJson(v)
case v: String => Json.toJson(v)
case v: Boolean => Json.toJson(v)
case _ => Json.toJson("Unrecognized type")
}
}
If you'd like to see which DefaultWrites are available you can browse them in the play.api.libs.json package in trait DefaultWrites
for example:
/**
* Serializer for Boolean types.
*/
implicit object BooleanWrites extends Writes[Boolean] {
def writes(o: Boolean) = JsBoolean(o)
}

Arbitrary JSON keys with Aeson - Haskell

I have a bunch of nested JSON objects with arbitrary keys.
{
"A": {
"B": {
"C": "hello"
}
}
}
Where A, B, C are unknown ahead of time. Each of those three could also
have siblings.
I'm wondering if there is a way to parse this into a custom type with Aeson in
some elegant way. What I have been doing is loading it into an Aeson Object.
How would you go about implementing the FromJSON for this kind of JSON
object?
Thanks!
Edit:
{
"USA": {
"California": {
"San Francisco": "Some text"
}
},
"Canada": {
...
}
}
This should compile to CountryDatabase where...
type City = Map String String
type Country = Map String City
type CountryDatabase = Map String Country
You can reuse FromJSON instance of Map String v. Something like the next:
{-# LANGUAGE OverloadedStrings #-}
import Data.Functor
import Data.Monoid
import Data.Aeson
import Data.Map (Map)
import qualified Data.ByteString.Lazy as LBS
import System.Environment
newtype City = City (Map String String)
deriving Show
instance FromJSON City where
parseJSON val = City <$> parseJSON val
newtype Country = Country (Map String City)
deriving Show
instance FromJSON Country where
parseJSON val = Country <$> parseJSON val
newtype DB = DB (Map String Country)
deriving Show
instance FromJSON DB where
parseJSON val = DB <$> parseJSON val
main :: IO ()
main = do
file <- head <$> getArgs
str <- LBS.readFile file
print (decode str :: Maybe DB)
The output:
shum#shum-lt:/tmp/shum$ cat in.js
{
"A": {
"A1": {
"A11": "1111",
"A22": "2222"
}
},
"B": {
}
}
shum#shum-lt:/tmp/shum$ runhaskell test.hs in.js
Just (DB (fromList [("A",Country (fromList [("A1",City (fromList [("A11","1111"),("A22","2222")]))])),("B",Country (fromList []))]))
shum#shum-lt:/tmp/shum$
PS: You can do it without newtypes, I used them only for clarity.