Skip to content

Instantly share code, notes, and snippets.

@agemooij
Last active November 18, 2020 14:34
Show Gist options
  • Star 16 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save agemooij/7679130 to your computer and use it in GitHub Desktop.
Save agemooij/7679130 to your computer and use it in GitHub Desktop.
An example of how to customize the field mapping in spray-json to do generic transformation, in this case translating between camelcased Scala attributes and snakecase JSON attributes
/**
* The MIT License (MIT)
*
* Copyright (c) 2013-2015 Andrew Snare, Age Mooij
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.scalapenos.spray
import spray.json._
/**
* A custom version of the Spray DefaultJsonProtocol with a modified field naming strategy
*/
trait SnakifiedSprayJsonSupport extends DefaultJsonProtocol {
import reflect._
/**
* This is the most important piece of code in this object!
* It overrides the default naming scheme used by spray-json and replaces it with a scheme that turns camelcased
* names into snakified names (i.e. using underscores as word separators).
*/
override protected def extractFieldNames(classTag: ClassTag[_]) = {
import java.util.Locale
def snakify(name: String) = PASS2.replaceAllIn(PASS1.replaceAllIn(name, REPLACEMENT), REPLACEMENT).toLowerCase(Locale.US)
super.extractFieldNames(classTag).map { snakify(_) }
}
private val PASS1 = """([A-Z]+)([A-Z][a-z])""".r
private val PASS2 = """([a-z\d])([A-Z])""".r
private val REPLACEMENT = "$1_$2"
}
object SnakifiedSprayJsonSupport extends SnakifiedSprayJsonSupport
@agemooij
Copy link
Author

This code was adapted by me from an original idea and implementation by @asnare

@jaytaylor
Copy link

Thank you, this was immensely helpful!

@longliveenduro
Copy link

Awesome! Thanks!

@asnare
Copy link

asnare commented Aug 3, 2014

So, a few minutes ago I wanted to do this once more and no longer had access to my original implementation. Amusingly Googling to see if there was a better way of doing this by now led me here…

Thanks for preserving this. :)

@agemooij
Copy link
Author

agemooij commented Mar 9, 2015

Now officially licensed with the MIT license

@sulika
Copy link

sulika commented May 20, 2016

Thanks. Really helpful.

@wppark
Copy link

wppark commented Sep 28, 2016

what a great code! thank you so much

@thepiwo
Copy link

thepiwo commented Sep 19, 2018

great code! will use this

@sm-tester
Copy link

sm-tester commented Oct 30, 2020

What about combined json field names(I mean fields with underscore and not underscore)? for example I have following json:

{ "requestId": "sdsdsdsdsd", "_links": { "self": { "href": "https://ecom2.nbu.uz/v1/Orders/f963d8c1-fe19-44e6-8441-e6e496b399b5" }, "redirectToCheckout": { "href": "https://ecom2.nbu.uz/Checkout/f963d8c1-fe19-44e6-8441-e6e496b399b5" } } }

Your solution not works here, because spray json will asks "request_id" field. Can you give any suggestions by that situation?

@agemooij
Copy link
Author

@sm-tester this code is ancient and nowadays I would not use it myself since spray-json has explicit support for defining the exact field names you want to map to.

Use something like this:

val input = """{ "requestId": "sdsdsdsdsd", "_links": { "self": { "href": "https://ecom2.nbu.uz/v1/Orders/f963d8c1-fe19-44e6-8441-e6e496b399b5" }, "redirectToCheckout": { "href": "https://ecom2.nbu.uz/Checkout/f963d8c1-fe19-44e6-8441-e6e496b399b5" } } }"""

case class Input(...)

implicit val format = jsonFormat(Input, "requestId", "_links")

e.g. you can map the original fields to whatever you call your fields in the case class by just specifying the names explicitly.

So please do not use this ancient code and instead use the standard spray-json features.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment