I use the Scala circe library to convert objects of the case class Message to JSON and also to create Message objects from their JSON representation. This class is implemented as follows:
import io.circe
import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder}
import io.circe.parser
import io.circe.syntax._
object Message {
implicit val measurementDecoder = deriveDecoder[Message]
implicit val measurementEncoder = deriveEncoder[Message]
def fromJson(jsonString: String): Either[circe.Error, Message] =
parser.decode[Message](jsonString)
}
case class Message(id: Int, text: String) {
def toJson() = (this).asJson.noSpaces
def toJson2() = this.asJson.noSpaces // syntax error: No implicit arguments of type: Encoder[Message.this.type]
}
My point is the implementation of the method toJson. While this variant works
def toJson() = (this).asJson.noSpaces
the variant
def toJson() = this.asJson.noSpaces
leads to the syntax error
No implicit arguments of type: Encoder[Message.this.type]
So what's the difference between this and (this) in Scala?
As pointed out by Luis Miguel Mejía Suárez it is neither a syntax nor a compile error, but a bug in the current version of the IntelliJ IDEA Scala plugin (version 2021.3.17). Using parentheses is a way to get around this problem.
Related
I have a case class containing a field of password. For safety I need to mask it when converting to Json.
So I create a custom serializer for this, as below.
import org.json4s.CustomSerializer
import org.json4s._
import scala.runtime.ScalaRunTime
import org.json4s.jackson.JsonMethods._
case class UserInfo(
userid: Long,
username: Option[String],
password: Option[String]
) {
override def toString: String = {
val ui = this.copy(password = password.map(_ => "******"))
ScalaRunTime._toString(ui)
}
}
case object UserInfoSerializer extends CustomSerializer[UserInfo](format => ({
case jsonObj: JObject =>
implicit val formats = DefaultFormats
jsonObj.extract[UserInfo]
}, {
case ui: UserInfo =>
implicit val formats = DefaultFormats
Extraction.decompose(ui.copy(password = ui.password.map(_ => "******")))
}))
implicit val formats = DefaultFormats + UserInfoSerializer
But when I try to convert val ui = UserInfo(123, Some("anonymous"), Some("xxx")) to a Json string by write(render(ui)), it always fails with
scala> render(ui)
<console>:22: error: type mismatch;
found : UserInfo
required: org.json4s.JValue
(which expands to) org.json4s.JsonAST.JValue
render(ui)
I have to use it as render(Extraction.decompose(ui)), or add an implicit conversion from UserInfo to JValue as implicit def userInfo2JValue(ui: UserInfn) = Extraction.decompose(ui)
What's the right way to make the custom serializer work as default ones?
Method render() simply renders JSON AST, it does not know how to convert an instance of your class to JValue. Look at this diagram, which illustrates data transformations with Json4s. Long story short, if you want to render your class as a JSON string, you can first convert it to JValue and then render like you did:
render(Extraction.decompose(ui))
or you can take a shortcut and use Serialization.write which does both operations internally:
Serialization.write(ui)
In either case, it is going to use your custom serializer if it has been added the explicit formats.
I use this library https://christopherdavenport.github.io/fuuid/ for creating ID of custom object and persist them into databse.
I have a simple case class which is my model:
import io.chrisdavenport.fuuid.FUUID
case class Bet(
betId: Option[FUUID],
home: String,
away: String,
stake: BigDecimal,
betType: String)
I used FUUID here as an Option parameter. I have also a routes created with Http4s which should take json from input and map it into model:
class BettingRoutes[F[_] : Async](service: BettingService[F]) extends Http4sDsl[F] {
def routes: HttpRoutes[F] = HttpRoutes.of[F] {
case req#PUT -> Root / "bets" =>
for {
bet <- req.as[Bet]
created <- service.put(bet)
response <- Created(created)
} yield response
}
}
I also added some implicits to encode and decode from Circe:
object jsons {
implicit def circeDecoder[A[_] : Sync, B: Decoder]: EntityDecoder[A, B] = jsonOf[A, B]
implicit def circeEncoder[A[_] : Sync, B: Encoder]: EntityEncoder[A, B] = jsonEncoderOf[A, B]
}
The problem is - when I want to compile project, I got an errors like this in route class:
Error:(23, 22) Cannot decode into a value of type model.Bet, because no EntityDecoder[F, model.Bet] instance could be found.
bet <- req.as[Bet]
Error:(23, 22) not enough arguments for method as: (implicit F: cats.Functor[F], implicit decoder: org.http4s.EntityDecoder[F,model.Bet])F[model.Bet].
Unspecified value parameter decoder.
bet <- req.as[Bet]
Error:(25, 28) Cannot convert from model.Bet to an Entity, because no EntityEncoder[F, model.Bet] instance could be found.
response <- Created(created)
etc. I investigated it and it appears because of using FUUID. I changed all FUUID classes to Long and after this just to java's UUID and then everything compile correctly without errors. The problem is only with FUUID and probably with conversion of it. I tried to use Circe Integration as It was shown in FUUID link above, but it did not help. Do you know how to fix this code to compile everything with fuuid and circe?
I am new to cats and connected libs, so maybe it is a simple mistake, but it is not trivial for me now.
In order to have EntityDecoder[F, Bet] via jsons.circeDecoder we firstly need Decoder[Bet]. It can be auto-generated by Circe if we have decoders for all fields. The thing is there is Decoder[UUID] but no Decoder[FUUID].
So just define necessary implicit
implicit val fuuidDecoder: Decoder[FUUID] = Decoder[UUID].map(FUUID.fromUUID)
Similarly for encoders
implicit val fuuidEncoder: Encoder[FUUID] = Encoder[UUID].contramap(FUUID.Unsafe.toUUID)
I am trying to write a test for a Post request
here is my code :
val request = CreateLinkRequest(token = Some(validToken),billing_ref_id = Some("123"), store_id = Some("123"), agent_id = Some("123"))
val endPoint = Uri(this.serverRootUrl + "path").toString
val post = Post(endPoint, request)
val pipeline = jsonAsStringPipeline(post)
val responseContents = Await.ready(pipeline, atMost = callMaxWaitDuration)
But this doesnt compile, I keep getting this error :
Error:(156, 20) could not find implicit value for evidence parameter of type spray.httpx.marshalling.Marshaller[CreateLinkSpec.this.CreateLinkRequest]
val post = Post(endPoint, request)
^
Error:(156, 20) not enough arguments for method apply: (implicit evidence$1:
spray.httpx.marshalling.Marshaller[CreateLinkSpec.this.CreateLinkRequest])spray.http.HttpRequest in class RequestBuilder.
Unspecified value parameter evidence$1.
val post = Post(endPoint, request)
^
What does this mean?
How can I fix it ?
EDIT:
this is the json in the body :
{ token:"123", billing_ref_id:"123", store_id:"123", agent_id:"123"}
and this is the object for it in the code:
private final case class CreateLinkRequest(
token: Option[String] = Some(validToken),
billing_ref_id: Option[String] = Some(Random.alphanumeric.take(10).mkString),
agent_id: Option[String] = Some(Random.alphanumeric.take(3).mkString),
store_id: Option[String] = Some(Random.alphanumeric.take(3).mkString)
)
You are trying to call Post method which takes implicit Marshaller as parameter. Note that implicit parameters don't have to be provided as long as the compiler can find one in the scope (check this for more info about implicits: Where does Scala look for implicits?)
However, your code doesn't have any implicit Marshaller defined so the compiler doesn't know how to convert your case class into the HttpEntity.
In your case you want it to be converted into the HttpEntity with Content-Type: application/json. To do that you just need to define: implicit val CreateLinkRequestMarshaller: Marshaller[CreateLinkRequest]. This tells scala how to convert your case class into the HttpEntity.
You also want it to be passed to the context as JSON, so we are going to define our JsonProtocol, i.e. MyJsonProtocol.
package test
import spray.http.HttpEntity
import spray.http._
import spray.json._
import spray.httpx.marshalling.{Marshaller, MarshallingContext}
import test.TestMarshaller.CreateLinkRequest
object MyJsonProtocol extends DefaultJsonProtocol {
implicit def createLinkRequestFormat: JsonFormat[CreateLinkRequest] = jsonFormat4(CreateLinkRequest)
}
object TestMarshaller extends App {
import MyJsonProtocol._
case class CreateLinkRequest(token: Option[String], billing_ref_id: Option[String], store_id: Option[String], agent_id: Option[String])
implicit val CreateLinkRequestMarshaller: Marshaller[CreateLinkRequest] = new Marshaller[CreateLinkRequest] {
override def apply(value: CreateLinkRequest, ctx: MarshallingContext): Unit = {
val entity = HttpEntity(MediaTypes.`application/json`, value.toJson.compactPrint)
ctx.marshalTo(entity)
}
}
// Here goes your test
}
Note that you can define these implicits somewhere else, e.g. a package and then just import it in the test class. This would be better because you will certainly want to reuse the Marshaller.
I have the following object for making a conversion of an object ParsedItemDocument to a json String. I should note that ParsedItemDocument is a trait. My problem is that the implicit conversion that is called on the second snippet is not recognized by the compiler. Is there anything more that needs to be done for the implicit conversion to work?
import scala.language.implicitConversions
import wikidataParser.ParsedItemDocument
object Converters {
def toJson(obj: Any): String = {
val mapper = new ObjectMapper()
mapper.registerModule(DefaultScalaModule)
val out = new StringWriter
mapper.writeValue(out, obj)
return out.toString()
}
implicit def parsedItemDocumentToJsonString
(item: ParsedItemDocument): String = {
Converters.toJson(item)
}
}
Now, I use the following code-snippet in my code
import tools.Converters._
import wikidataParser.ParsedItemDocument
class WikipediaRankingTester2 extends FlatSpec {
"It" should "do something" in {
val jsonrdd:RDD[String]=rankedItems.map(t:Long,ParsedItemDocument)=>
t._2.parsedItemDocumentToJsonString)//compilation error here
}
}
You are mixing up implicit conversions and implicit classes.
If you want to use parsedItemDocumentToJsonString as a "method" of an object of type ParsedItemDocument, then you would need to define your implicit as
implicit class JSONParsing(item: ParsedItemDocument): String {
def parsedItemDocumentToJsonString = Converters.toJson(item)
}
If you declare it as an implicit conversion, as you did, then it means that you can call any methods of String on an object of type ParsedItemDocument, as the object will be implicitly converted to a String through the implicit method.
Also, it is not great practice to declare an entire implicit class / conversion, unless you 1) cannot add it to the original class, or 2) will be reusing the conversion very often, and it would save great amounts of code/readability. This does not seem to be the case here, as you are only wrapping in Converters.toJSON, which is not very verbose, and jsut as readable.
PS: your syntax in your "map" is wrong, the right syntax would be
val jsonrdd = rankedItems.map(t => t._2.parsedItemDocumentToJsonString)
If your implicit is working this should do it:
rankedItems.map(t => t._2)
You can test this by making the call explicit
rankedItems.map(t => parsedItemDocumentToJsonString(t._2))
If you want to add an extra method to a ParsedItemDocument you could use an implicit class, I don't think you need it but your code looks a bit that way.
I'm new with Salat,Casbah and MongoDB. When I've been trying to make a simple method to get all users from db,
import DAL.Instances.User.{UserDAO, User}
import com.novus.salat._
import com.novus.salat.global._
import com.novus.salat.annotations._
import com.novus.salat.dao._
import com.mongodb.casbah.Imports._
import com.mongodb.casbah.MongoConnection
object UserRepository {
def getAllUsers() = {
val userList= UserDAO.find()
userList.isEmpty match {
case true => throw new Exception("None users in your db")
case false => userList
}
}
I faced with two errors:
Error:(29, 31) No implicit view available from Unit => com
.mongodb.DBObject.
val userList= UserDAO.find()
^
Error:(29, 31) not enough arguments for method find: (implicit evidence$2: Unit => com.mongodb.DBObject)com.novus.salat.dao.SalatMongoCursor[DAL.Instances.User.User].
Unspecified value parameter evidence$2.
val userList= UserDAO.find()
^
Here is my User code:
object User {
case class User( _id: ObjectId = new ObjectId, name:String, age:Int)
object UserDAO extends SalatDAO[User, ObjectId](collection = MongoConnection()("fulltestdb")("user"))
}
I'm not sure what version of Salat you are using but if you look at the signature for find it'll give you a clue as to what the issue is:
def find[A <% DBObject](ref: A): SalatMongoCursor[ObjectType]
You need to call find with a parameter that has a view bound so that this parameter may be viewed as a DBObject. This means that an implicit conversion from A => DBObject is expected to be in scope.
In your case you aren't passing any parameter. This is being treated as Unit and so the compiler tries to find an implicit conversion from Unit => DBObject. This can't be found so compilation fails.
To fix this you're best bet is to pass in an empty DBObject, you can achieve this with MongoDBObject.empty from casbah. You could add an implicit conversion from Unit => MongoDBObject but I'd probably lean towards making it explicit where possible.