From 804755b17b90599cb3df2096be10671c0a347fca Mon Sep 17 00:00:00 2001 From: xuwei-k <6b656e6a69@gmail.com> Date: Tue, 5 May 2020 16:08:20 +0900 Subject: [PATCH 01/17] Update README --- README.md | 1040 +---------------------------------------------------- 1 file changed, 2 insertions(+), 1038 deletions(-) diff --git a/README.md b/README.md index c6b396979..23f9857b0 100644 --- a/README.md +++ b/README.md @@ -1,1040 +1,4 @@ -# JSON4S [![Build Status](https://travis-ci.org/json4s/json4s.svg?branch=3.6)](https://travis-ci.org/json4s/json4s) -[![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.json4s/json4s-core_2.12/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.json4s/json4s-core_2.12) +# JSON4S [![Build Status](https://travis-ci.com/json4s/json4s.svg?branch=3.6)](https://travis-ci.com/json4s/json4s) -[![Join the chat at https://gitter.im/json4s/json4s](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/json4s/json4s?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +see [default branch README](https://github.com/json4s/json4s) -At this moment there are at least 6 json libraries for scala, not counting the java json libraries. -All these libraries have a very similar AST. This project aims to provide a single AST to be used by other scala -json libraries. - -At this moment the approach taken to working with the AST has been taken from lift-json and the native package -is in fact lift-json but outside of the lift project. - -## Lift JSON - -This project also attempts to set lift-json free from the release schedule imposed by the lift framework. -The Lift framework carries many dependencies and as such it's typically a blocker for many other scala projects when -a new version of scala is released. - -So the native package in this library is in fact verbatim lift-json in a different package name; this means that -your import statements will change if you use this library. - -```scala -import org.json4s._ -import org.json4s.native.JsonMethods._ -``` - -After that everything works exactly the same as it would with lift-json - -## Jackson - -In addition to the native parser there is also an implementation that uses jackson for parsing to the AST. -The jackson module includes most of the jackson-module-scala functionality and the ability to use it with the -lift-json AST. - -To use jackson instead of the native parser: - -```scala -import org.json4s._ -import org.json4s.jackson.JsonMethods._ -``` - -Be aware that the default behavior of the jackson integration is to close the stream when it's done. -If you want to change that: - -```scala -import com.fasterxml.jackson.databind.SerializationFeature -org.json4s.jackson.JsonMethods.mapper.configure(SerializationFeature.CLOSE_CLOSEABLE, false) -``` - -## Guide - -Parsing and formatting utilities for JSON. - -A central concept in lift-json library is Json AST which models the structure of -a JSON document as a syntax tree. - -```scala -sealed abstract class JValue -case object JNothing extends JValue // 'zero' for JValue -case object JNull extends JValue -case class JString(s: String) extends JValue -case class JDouble(num: Double) extends JValue -case class JDecimal(num: BigDecimal) extends JValue -case class JInt(num: BigInt) extends JValue -case class JLong(num: Long) extends JValue -case class JBool(value: Boolean) extends JValue -case class JObject(obj: List[JField]) extends JValue -case class JArray(arr: List[JValue]) extends JValue - -type JField = (String, JValue) -``` - -All features are implemented in terms of the above AST. Functions are used to transform -the AST itself, or to transform the AST between different formats. Common transformations -are summarized in a following picture. - -![Json AST](https://raw.github.com/json4s/json4s/3.6/core/json.png) - -Summary of the features: - -* Fast JSON parser -* LINQ-style queries -* Case classes can be used to extract values from parsed JSON -* Diff & merge -* DSL to produce valid JSON -* XPath-like expressions and HOFs to manipulate JSON -* Pretty and compact printing -* XML conversions -* Serialization -* Low-level pull parser API - -Installation -============ - -You can add the json4s as a dependency in following ways. Note, replace {latestVersion} with correct Json4s version. - -You can find available versions here: - -http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.json4s%22 - -### SBT users - -For the native support add the following dependency to your project description: - -```scala -val json4sNative = "org.json4s" %% "json4s-native" % "{latestVersion}" -``` - -For the Jackson support add the following dependency to your project description: - -```scala -val json4sJackson = "org.json4s" %% "json4s-jackson" % "{latestVersion}" -``` - -### Maven users - -For the native support add the following dependency to your pom: - -```xml - - org.json4s - json4s-native_${scala.version} - {latestVersion} - -``` - -For the jackson support add the following dependency to your pom: - -```xml - - org.json4s - json4s-jackson_${scala.version} - {latestVersion} - -``` - -Extras ------- - -* [ext](https://github.com/json4s/json4s/tree/3.6/ext) - -Support for Enum, Joda-Time, ... - -* [scalaz](https://github.com/json4s/json4s/tree/3.6/scalaz) - -Applicative style parsing with Scalaz - -Migration from older versions -============================= - -3.3.0 -> --------- - -json4s 3.3 basically should be source code compatible with 3.2.x. Since json4s 3.3.0, We've started using [MiMa](https://github.com/typesafehub/migration-manager) for binary compatibility verification not to repeat the bin compatibility issue described [here](https://github.com/json4s/json4s/issues/225). - -The behavior of `.toOption` on JValue has changed. Now both `JNothing` and `JNull` return None. -For the old behavior you can use `toSome` which will only turn a `JNothing` into a None. - -All the merged pull requests: -https://github.com/json4s/json4s/pulls?q=is%3Apr+is%3Aclosed+milestone%3A3.3 - -3.0.0 -> --------- - -JField is no longer a JValue. This means more type safety since it is no longer possible -to create invalid JSON where JFields are added directly into JArrays for instance. The most -noticeable consequence of this change are that map, transform, find and filter come in -two versions: - -```scala -def map(f: JValue => JValue): JValue -def mapField(f: JField => JField): JValue -def transform(f: PartialFunction[JValue, JValue]): JValue -def transformField(f: PartialFunction[JField, JField]): JValue -def find(p: JValue => Boolean): Option[JValue] -def findField(p: JField => Boolean): Option[JField] -//... -``` - -Use *Field functions to traverse fields in the JSON, and use the functions without 'Field' -in the name to traverse values in the JSON. - -2.2 -> ------- - -Path expressions were changed after version 2.2. Previous versions returned JField, which -unnecessarily complicated the use of the expressions. If you have used path expressions -with pattern matching like: - -```scala -val JField("bar", JInt(x)) = json \ "foo" \ "bar" -``` - -it is now required to change that to: - -```scala -val JInt(x) = json \ "foo" \ "bar" -``` - -Parsing JSON -============ - -Any valid json can be parsed into internal AST format. -For native support: - -```scala -scala> import org.json4s._ -scala> import org.json4s.native.JsonMethods._ - -scala> parse(""" { "numbers" : [1, 2, 3, 4] } """) -res0: org.json4s.JsonAST.JValue = - JObject(List((numbers,JArray(List(JInt(1), JInt(2), JInt(3), JInt(4)))))) - -scala> parse("""{"name":"Toy","price":35.35}""", useBigDecimalForDouble = true) -res1: org.json4s.package.JValue = - JObject(List((name,JString(Toy)), (price,JDecimal(35.35)))) -``` - -For jackson support: - -```scala -scala> import org.json4s._ -scala> import org.json4s.jackson.JsonMethods._ - -scala> parse(""" { "numbers" : [1, 2, 3, 4] } """) -res0: org.json4s.JsonAST.JValue = - JObject(List((numbers,JArray(List(JInt(1), JInt(2), JInt(3), JInt(4)))))) - -scala> parse("""{"name":"Toy","price":35.35}""", useBigDecimalForDouble = true) -res1: org.json4s.package.JValue = - JObject(List((name,JString(Toy)), (price,JDecimal(35.35)))) -``` - -Producing JSON -============== - -You can generate json in 2 modes: either in `DoubleMode` or in `BigDecimalMode`; the former will map all decimal values -into JDoubles, and the latter into JDecimals. - -For the double mode dsl use: - -```scala -import org.json4s.JsonDSL._ -// or -import org.json4s.JsonDSL.WithDouble._ -``` - -For the big decimal mode dsl use: - -```scala -import org.json4s.JsonDSL.WithBigDecimal._ -``` - - -DSL rules ---------- - -* Primitive types map to JSON primitives. -* Any seq produces JSON array. - -```scala -scala> val json = List(1, 2, 3) - -scala> compact(render(json)) -res0: String = [1,2,3] -``` - -* Tuple2[String, A] produces field. - -```scala -scala> val json = ("name" -> "joe") - -scala> compact(render(json)) -res1: String = {"name":"joe"} -``` - -* ~ operator produces object by combining fields. - -```scala -scala> val json = ("name" -> "joe") ~ ("age" -> 35) - -scala> compact(render(json)) -res2: String = {"name":"joe","age":35} -``` - -* Any value can be optional. The field and value are completely removed when it doesn't have a value. - -```scala -scala> val json = ("name" -> "joe") ~ ("age" -> Some(35)) - -scala> compact(render(json)) -res3: String = {"name":"joe","age":35} - -scala> val json = ("name" -> "joe") ~ ("age" -> (None: Option[Int])) - -scala> compact(render(json)) -res4: String = {"name":"joe"} -``` - -* Extending the dsl - -To extend the dsl with your own classes you must have an implicit conversion in scope of signature: - -```scala -type DslConversion = T => JValue -``` - -Example -------- - -```scala -object JsonExample extends App { - import org.json4s._ - import org.json4s.JsonDSL._ - import org.json4s.jackson.JsonMethods._ - - case class Winner(id: Long, numbers: List[Int]) - case class Lotto(id: Long, winningNumbers: List[Int], winners: List[Winner], drawDate: Option[java.util.Date]) - - val winners = List(Winner(23, List(2, 45, 34, 23, 3, 5)), Winner(54, List(52, 3, 12, 11, 18, 22))) - val lotto = Lotto(5, List(2, 45, 34, 23, 7, 5, 3), winners, None) - - val json = - ("lotto" -> - ("lotto-id" -> lotto.id) ~ - ("winning-numbers" -> lotto.winningNumbers) ~ - ("draw-date" -> lotto.drawDate.map(_.toString)) ~ - ("winners" -> - lotto.winners.map { w => - (("winner-id" -> w.id) ~ - ("numbers" -> w.numbers))})) - - println(compact(render(json))) -} -``` - -```scala -scala> JsonExample -{"lotto":{"lotto-id":5,"winning-numbers":[2,45,34,23,7,5,3],"winners": -[{"winner-id":23,"numbers":[2,45,34,23,3,5]},{"winner-id":54,"numbers":[52,3,12,11,18,22]}]}} -``` - -The above example produces the following pretty-printed JSON. Notice that draw-date field is not rendered since its value is None: - -```scala -scala> pretty(render(JsonExample.json)) - -{ - "lotto":{ - "lotto-id":5, - "winning-numbers":[2,45,34,23,7,5,3], - "winners":[{ - "winner-id":23, - "numbers":[2,45,34,23,3,5] - },{ - "winner-id":54, - "numbers":[52,3,12,11,18,22] - }] - } -} -``` - -Merging & Diffing ------------------ - -Two JSONs can be merged and diffed with each other. -Please see more examples in [MergeExamples.scala](https://github.com/json4s/json4s/blob/3.6/tests/src/test/scala/org/json4s/MergeExamples.scala) and [DiffExamples.scala](https://github.com/json4s/json4s/blob/3.6/tests/src/test/scala/org/json4s/DiffExamples.scala). - -```scala -scala> import org.json4s._ -scala> import org.json4s.jackson.JsonMethods._ - -scala> val lotto1 = parse("""{ - "lotto":{ - "lotto-id":5, - "winning-numbers":[2,45,34,23,7,5,3], - "winners":[{ - "winner-id":23, - "numbers":[2,45,34,23,3,5] - }] - } - }""") - -scala> val lotto2 = parse("""{ - "lotto":{ - "winners":[{ - "winner-id":54, - "numbers":[52,3,12,11,18,22] - }] - } - }""") - -scala> val mergedLotto = lotto1 merge lotto2 - -scala> pretty(render(mergedLotto)) -res0: String = -{ - "lotto":{ - "lotto-id":5, - "winning-numbers":[2,45,34,23,7,5,3], - "winners":[{ - "winner-id":23, - "numbers":[2,45,34,23,3,5] - },{ - "winner-id":54, - "numbers":[52,3,12,11,18,22] - }] - } -} - -scala> val Diff(changed, added, deleted) = mergedLotto diff lotto1 -changed: org.json4s.JsonAST.JValue = JNothing -added: org.json4s.JsonAST.JValue = JNothing -deleted: org.json4s.JsonAST.JValue = JObject(List((lotto,JObject(List(JField(winners, -JArray(List(JObject(List((winner-id,JInt(54)), (numbers,JArray( -List(JInt(52), JInt(3), JInt(12), JInt(11), JInt(18), JInt(22)))))))))))))) -``` - -Querying JSON -============= - -"LINQ" style ------------- - -JSON values can be extracted using for-comprehensions. -Please see more examples in [JsonQueryExamples.scala](https://github.com/json4s/json4s/blob/3.6/tests/src/test/scala/org/json4s/JsonQueryExamples.scala). - -```scala -scala> import org.json4s._ -scala> import org.json4s.native.JsonMethods._ - -scala> val json = parse(""" - { "name": "joe", - "children": [ - { - "name": "Mary", - "age": 5 - }, - { - "name": "Mazy", - "age": 3 - } - ] - } - """) - -scala> for { - JObject(child) <- json - JField("age", JInt(age)) <- child - } yield age -res0: List[BigInt] = List(5, 3) - -scala> for { - JObject(child) <- json - JField("name", JString(name)) <- child - JField("age", JInt(age)) <- child - if age > 4 - } yield (name, age) -res1: List[(String, BigInt)] = List((Mary,5)) -``` - -XPath + HOFs ------------- - -The json AST can be queried using XPath-like functions. The following REPL session shows the usage of -'\\', '\\\\', 'find', 'filter', 'transform', 'remove' and 'values' functions. - -The example json is: - -```javascript -{ - "person": { - "name": "Joe", - "age": 35, - "spouse": { - "person": { - "name": "Marilyn", - "age": 33 - } - } - } -} -``` - -Translated to DSL syntax: - -```scala -scala> import org.json4s._ -scala> import org.json4s.native.JsonMethods._ -``` - -or - -```scala -scala> import org.json4s.jackson.JsonMethods._ -scala> import org.json4s.JsonDSL._ - -scala> val json: JObject = - ("person" -> - ("name" -> "Joe") ~ - ("age" -> 35) ~ - ("spouse" -> - ("person" -> - ("name" -> "Marilyn") ~ - ("age" -> 33) - ) - ) - ) - -scala> json \\ "spouse" -res0: org.json4s.JsonAST.JValue = JObject(List( - (person,JObject(List((name,JString(Marilyn)), (age,JInt(33))))))) - -scala> compact(render(res0)) -res1: String = {"person":{"name":"Marilyn","age":33}} - -scala> compact(render(json \\ "name")) -res2: String = {"name":"Joe","name":"Marilyn"} - -scala> compact(render((json removeField { _ == JField("name", JString("Marilyn")) }) \\ "name")) -res3: String = "Joe" - -scala> compact(render(json \ "person" \ "name")) -res4: String = "Joe" - -scala> compact(render(json \ "person" \ "spouse" \ "person" \ "name")) -res5: String = "Marilyn" - -scala> json findField { - case JField("name", _) => true - case _ => false - } -res6: Option[org.json4s.JsonAST.JValue] = Some((name,JString(Joe))) - -scala> json filterField { - case JField("name", _) => true - case _ => false - } -res7: List[org.json4s.JsonAST.JField] = List(JField(name,JString(Joe)), JField(name,JString(Marilyn))) - -scala> json transformField { - case JField("name", JString(s)) => ("NAME", JString(s.toUpperCase)) - } -res8: org.json4s.JsonAST.JValue = JObject(List((person,JObject(List( -(NAME,JString(JOE)), (age,JInt(35)), (spouse,JObject(List( -(person,JObject(List((NAME,JString(MARILYN)), (age,JInt(33))))))))))))) - -scala> json.values -res8: scala.collection.immutable.Map[String,Any] = Map(person -> Map(name -> Joe, age -> 35, spouse -> Map(person -> Map(name -> Marilyn, age -> 33)))) -``` - -Indexed path expressions work too and values can be unboxed using type expressions: - -```scala -scala> val json = parse(""" - { "name": "joe", - "children": [ - { - "name": "Mary", - "age": 5 - }, - { - "name": "Mazy", - "age": 3 - } - ] - } - """) - -scala> (json \ "children")(0) -res0: org.json4s.JsonAST.JValue = JObject(List((name,JString(Mary)), (age,JInt(5)))) - -scala> (json \ "children")(1) \ "name" -res1: org.json4s.JsonAST.JValue = JString(Mazy) - -scala> json \\ classOf[JInt] -res2: List[org.json4s.JsonAST.JInt#Values] = List(5, 3) - -scala> json \ "children" \\ classOf[JString] -res3: List[org.json4s.JsonAST.JString#Values] = List(Mary, Mazy) -``` - -Extracting values -================= - -Case classes can be used to extract values from parsed JSON. Non-existent values can be extracted into scala.Option and strings can be automatically converted into java.util.Dates. - -Please see more examples in [ExtractionExampleSpec.scala](https://github.com/json4s/json4s/blob/3.6/tests/src/test/scala/org/json4s/ExtractionExamplesSpec.scala). - -```scala -scala> import org.json4s._ -scala> import org.json4s.jackson.JsonMethods._ - -scala> implicit val formats = DefaultFormats // Brings in default date formats etc. - -scala> case class Child(name: String, age: Int, birthdate: Option[java.util.Date]) -scala> case class Address(street: String, city: String) -scala> case class Person(name: String, address: Address, children: List[Child]) - -scala> val json = parse(""" - { "name": "joe", - "address": { - "street": "Bulevard", - "city": "Helsinki" - }, - "children": [ - { - "name": "Mary", - "age": 5, - "birthdate": "2004-09-04T18:06:22Z" - }, - { - "name": "Mazy", - "age": 3 - } - ] - } - """) - -scala> json.extract[Person] -res0: Person = Person(joe,Address(Bulevard,Helsinki),List(Child(Mary,5,Some(Sat Sep 04 18:06:22 EEST 2004)), Child(Mazy,3,None))) - -scala> val addressJson = json \ "address" // Extract address object -scala> addressJson.extract[Address] -res1: Address = Address(Bulevard,Helsinki) - -scala> (json \ "children").extract[List[Child]] // Extract list of objects -res2: List[Child] = List(Child(Mary,5,Some(Sat Sep 04 23:36:22 IST 2004)), Child(Mazy,3,None)) -``` - -By default the constructor parameter names must match json field names. However, sometimes json field names contain characters which are not allowed characters in Scala identifiers. There are two solutions for this. (See [LottoExample.scala](https://github.com/json4s/json4s/blob/3.6/tests/src/test/scala/org/json4s/LottoExample.scala) for a bigger example.) - -Use back ticks: - -```scala -scala> case class Person(`first-name`: String) -``` - -Use transform function to postprocess AST: - -```scala -scala> case class Person(firstname: String) -scala> json transformField { - case ("first-name", x) => ("firstname", x) - } -``` - -If the json field names are snake case (i.e., separated_by_underscores), but the case class uses camel case (i.e., firstLetterLowercaseAndNextWordsCapitalized), you can convert the keys during the extraction using `camelizeKeys`: - -```scala -scala> import org.json4s._ -scala> import org.json4s.native.JsonMethods._ -scala> implicit val formats = DefaultFormats -scala> val json = parse("""{"first_name":"Mary"}""") -scala> case class Person(firstName: String) - -scala> json.camelizeKeys.extract[Person] -res0: Person = Person(Mazy) -``` -See the "Serialization" section below for details on converting a class with camel-case fields into json with snake case keys. - -The extraction function tries to find the best-matching constructor when the case class has auxiliary constructors. For instance, extracting from JSON {"price":350} into the following case class will use the auxiliary constructor instead of the primary constructor: - -```scala -scala> case class Bike(make: String, price: Int) { - def this(price: Int) = this("Trek", price) - } -scala> parse(""" {"price":350} """).extract[Bike] -res0: Bike = Bike(Trek,350) -``` - -Primitive values can be extracted from JSON primitives or fields: - -```scala -scala> (json \ "name").extract[String] -res0: String = "joe" - -scala> ((json \ "children")(0) \ "birthdate").extract[Date] -res1: java.util.Date = Sat Sep 04 21:06:22 EEST 2004 -``` - -DateFormat can be changed by overriding 'DefaultFormats' (or by implementing trait 'Formats'): - -```scala -scala> implicit val formats = new DefaultFormats { - override def dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") - } -``` - -A JSON object can be extracted to Map[String, _] too. Each field becomes a key value pair -in result Map: - -```scala -scala> val json = parse(""" - { - "name": "joe", - "addresses": { - "address1": { - "street": "Bulevard", - "city": "Helsinki" - }, - "address2": { - "street": "Soho", - "city": "London" - } - } - }""") - -scala> case class PersonWithAddresses(name: String, addresses: Map[String, Address]) -scala> json.extract[PersonWithAddresses] -res0: PersonWithAddresses("joe", Map("address1" -> Address("Bulevard", "Helsinki"), - "address2" -> Address("Soho", "London"))) -``` - -Note that when the extraction of an `Option[_]` fails, the default behavior of `extract` is to return `None`. -You can make it fail with a [MappingException] by using a custom `Formats` object: - -```scala -val formats: Formats = DefaultFormats.withStrictOptionParsing -``` - -or - -```scala -val formats: Formats = new DefaultFormats { - override val strictOptionParsing: Boolean = true -} -``` - -Same happens with collections, the default behavior of `extract` is to return an empty instance of the collection. -You can make it fail with a [MappingException] by using a custom `Formats` object: - -```scala -val formats: Formats = DefaultFormats.withStrictArrayExtraction -``` - -or - -```scala -val formats: Formats = new DefaultFormats { - override val strictArrayExtraction: Boolean = true -} -``` - -Both these settings (`strictOptionParsing` and `strictArrayExtraction`) can be enabled with - -```scala -val formats: Formats = DefaultFormats.strict -``` - -Serialization -============= - -Case classes can be serialized and deserialized. Please see other examples in [SerializationExamples.scala](https://github.com/json4s/json4s/blob/3.6/tests/src/test/scala/org/json4s/native/SerializationExamples.scala). - -```scala -scala> import org.json4s._ -scala> import org.json4s.native.Serialization -scala> import org.json4s.native.Serialization.{read, write} - -scala> implicit val formats = Serialization.formats(NoTypeHints) - -scala> val ser = write(Child("Mary", 5, None)) - -scala> read[Child](ser) -res1: Child = Child(Mary,5,None) -``` - -If you're using jackson instead of the native one: - -```scala -scala> import org.json4s._ -scala> import org.json4s.jackson.Serialization -scala> import org.json4s.jackson.Serialization.{read, write} - -scala> implicit val formats = Serialization.formats(NoTypeHints) - -scala> val ser = write(Child("Mary", 5, None)) - -scala> read[Child](ser) -res1: Child = Child(Mary,5,None) -``` - -Serialization supports: - -* Arbitrarily deep case-class graphs -* All primitive types, including BigInt and Symbol -* List, Seq, Array, Set and Map (note, keys of the Map must be strings: Map[String, _]) -* scala.Option -* java.util.Date -* Polymorphic Lists (see below) -* Recursive types -* Serialization of fields of a class (see below) -* Custom serializer functions for types that are not supported (see below) - -If the class contains camel-case fields (i.e: firstLetterLowercaseAndNextWordsCapitalized) but you want to produce a json string with snake casing (i.e., separated_by_underscores), you can use the `snakizeKeys` method: - -```scala -scala> val ser = write(Person("Mary")) -ser: String = {"firstName":"Mary"} - -scala> compact(render(parse(ser).snakizeKeys)) -res0: String = {"first_name":"Mary"} -``` - -Serializing polymorphic Lists ------------------------------ - -Type hints are required when serializing polymorphic (or heterogeneous) Lists. Serialized JSON objects -will get an extra field named 'jsonClass' (the name can be changed by overriding 'typeHintFieldName' from Formats). - -```scala -scala> trait Animal -scala> case class Dog(name: String) extends Animal -scala> case class Fish(weight: Double) extends Animal -scala> case class Animals(animals: List[Animal]) - -scala> implicit val formats = Serialization.formats(ShortTypeHints(List(classOf[Dog], classOf[Fish]))) - -scala> val ser = write(Animals(Dog("pluto") :: Fish(1.2) :: Nil)) -ser: String = {"animals":[{"jsonClass":"Dog","name":"pluto"},{"jsonClass":"Fish","weight":1.2}]} - -scala> read[Animals](ser) -res0: Animals = Animals(List(Dog(pluto), Fish(1.2))) -``` - -ShortTypeHints outputs the short classname for all instances of configured objects. FullTypeHints outputs the full -classname. Other strategies can be implemented by extending the TypeHints trait. - -Serializing fields of a class ------------------------------ - -To enable serialization of fields, a FieldSerializer can be added for some type: - -```scala -implicit val formats = DefaultFormats + FieldSerializer[WildDog]() -``` - -Now the type WildDog (and all subtypes) gets serialized with all its fields (+ constructor parameters). -FieldSerializer takes two optional parameters, which can be used to intercept the field serialization: - -```scala -case class FieldSerializer[A: Manifest]( - serializer: PartialFunction[(String, Any), Option[(String, Any)]] = Map(), - deserializer: PartialFunction[JField, JField] = Map() -) -``` - -Those PartialFunctions are called just before a field is serialized or deserialized. Some useful PFs to rename and ignore fields are provided: - -```scala -val dogSerializer = FieldSerializer[WildDog]( - renameTo("name", "animalname") orElse ignore("owner"), - renameFrom("animalname", "name")) - -implicit val formats = DefaultFormats + dogSerializer -``` - -Serializing classes defined in traits or classes ------------------------------------------------- - -We've added support for case classes defined in a trait. But they do need custom formats. I'll explain why and then how. - -##### Why? - -For classes defined in a trait it's a bit difficult to get to their companion object, which is needed to provide default values. We could punt on those but that brings us to the next problem, that the compiler generates an extra field in the constructor of such case classes. The first field in the constructor of those case classes is called `$outer` and is of type of the *defining trait*. So somehow we need to get an instance of that object, naively we could scan all classes and collect the ones that are implementing the trait, but when there are more than one: which one to take? - -##### How? - -I've chosen to extend the formats to include a list of companion mappings for those case classes. So you can have formats that belong to your modules and keep the mappings in there. That will then make default values work and provide the much needed `$outer` field. - -```scala -trait SharedModule { - case class SharedObj(name: String, visible: Boolean = false) -} - -object PingPongGame extends SharedModule -implicit val formats: Formats = - DefaultFormats.withCompanions(classOf[PingPongGame.SharedObj] -> PingPongGame) - -val inst = PingPongGame.SharedObj("jeff", visible = true) -val extr = Extraction.decompose(inst) -extr must_== JObject("name" -> JString("jeff"), "visible" -> JBool(true)) -extr.extract[PingPongGame.SharedObj] must_== inst -``` - -Serializing non-supported types -------------------------------- - -It is possible to plug in custom serializer + deserializer functions for any type. -Now, if we have a non-case class Interval (thus, not supported by default), we can still serialize it -by providing following serializer. - -```scala -scala> class Interval(start: Long, end: Long) { - val startTime = start - val endTime = end - } - -scala> class IntervalSerializer extends CustomSerializer[Interval](format => ( - { - case JObject(JField("start", JInt(s)) :: JField("end", JInt(e)) :: Nil) => - new Interval(s.longValue, e.longValue) - }, - { - case x: Interval => - JObject(JField("start", JInt(BigInt(x.startTime))) :: - JField("end", JInt(BigInt(x.endTime))) :: Nil) - } - )) - -scala> implicit val formats = Serialization.formats(NoTypeHints) + new IntervalSerializer -``` - -A custom serializer is created by providing two partial functions. The first evaluates to a value -if it can unpack the data from JSON. The second creates the desired JSON if the type matches. - -Extensions ----------- - -Module json4s-ext contains extensions to extraction and serialization. The following types are supported. - -```scala -// Lift's box -implicit val formats = org.json4s.DefaultFormats + new org.json4s.native.ext.JsonBoxSerializer - -// Scala enums -implicit val formats = org.json4s.DefaultFormats + new org.json4s.ext.EnumSerializer(MyEnum) -// or -implicit val formats = org.json4s.DefaultFormats + new org.json4s.ext.EnumNameSerializer(MyEnum) - -// Joda Time -implicit val formats = org.json4s.DefaultFormats ++ org.json4s.ext.JodaTimeSerializers.all -``` - -XML support -=========== - -JSON structure can be converted to XML nodes and vice versa. -Please see more examples in [XmlExamples.scala](https://github.com/json4s/json4s/blob/3.6/tests/src/test/scala/org/json4s/XmlExamples.scala). - -```scala -scala> import org.json4s.Xml.{toJson, toXml} -scala> val xml = - - - 1 - Harry - - - 2 - David - - - -scala> val json = toJson(xml) -scala> pretty(render(json)) -res3: String = -{ - "users":{ - "user":[{ - "id":"1", - "name":"Harry" - },{ - "id":"2", - "name":"David" - }] - } -} -``` - -Now, the above example has two problems. First, the ID is converted to String while we might want it as an Int. This is easy to fix by mapping JString(s) to JInt(s.toInt). The second problem is more subtle. The conversion function decides to use a JSON array because there's more than one `user` element in XML. Therefore a structurally equivalent XML document which happens to have just one `user` element will generate a JSON document without a JSON array. This is rarely a desired outcome. These both problems can be fixed by the following transformation function. - -```scala -scala> json transformField { - case ("id", JString(s)) => ("id", JInt(s.toInt)) - case ("user", x: JObject) => ("user", JArray(x :: Nil)) - } -``` - -Other direction is supported too. Converting JSON to XML: - -```scala -scala> toXml(json) -res5: scala.xml.NodeSeq = NodeSeq(1Harry2David) -``` - -Low-level pull parser API -========================= - -The pull parser API is provided for cases requiring extreme performance. It improves parsing performance in two ways. First, no intermediate AST is generated. Second, you can stop parsing at any time, skipping the rest of the stream. Note: This parsing style is recommended only as an optimization. The above-mentioned functional APIs are easier to use. - -Consider the following example, which shows how to parse one field value from a big JSON: - -```scala -scala> val json = """ - { - ... - "firstName": "John", - "lastName": "Smith", - "address": { - "streetAddress": "21 2nd Street", - "city": "New York", - "state": "NY", - "postalCode": 10021 - }, - "phoneNumbers": [ - { "type": "home", "number": "212 555-1234" }, - { "type": "fax", "number": "646 555-4567" } - ], - ... - }""" - -scala> val parser = (p: Parser) => { - def parse: BigInt = p.nextToken match { - case FieldStart("postalCode") => p.nextToken match { - case IntVal(code) => code - case _ => p.fail("expected int") - } - case End => p.fail("no field named 'postalCode'") - case _ => parse - } - - parse - } - -scala> val postalCode = parse(json, parser) -postalCode: BigInt = 10021 -``` - -The pull parser is a function `Parser => A`; in this example it is concretely `Parser => BigInt`. -The constructed parser recursively reads tokens until it finds a `FieldStart("postalCode")` token. -After that the next token must be `IntVal`; otherwise parsing fails. It returns the parsed integer value and stops parsing immediately. - -Kudos -===== - -* The original idea for the DSL syntax was taken from the Lift mailing list ([by Marius](http://markmail.org/message/lniven2hn22vhupu)). - -* The idea for the AST and rendering was taken from [Real World Haskell book](http://book.realworldhaskell.org/read/writing-a-library-working-with-json-data.html). From f45eb5b561e30444037191174bc895cec2b8622d Mon Sep 17 00:00:00 2001 From: xuwei-k <6b656e6a69@gmail.com> Date: Thu, 27 Jun 2019 10:34:36 +0900 Subject: [PATCH 02/17] use Map.apply in test. revert workaround for Scala 2.13.0-M5 (cherry picked from commit 08a5a7f569b22254703de4175838083241d74db4) --- .../org/json4s/native/MapSerializationExamples.scala | 8 ++------ .../scala/org/json4s/native/SerializationExamples.scala | 8 ++------ 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/tests/src/test/scala/org/json4s/native/MapSerializationExamples.scala b/tests/src/test/scala/org/json4s/native/MapSerializationExamples.scala index 6c64d7d8c..0d6d881c9 100644 --- a/tests/src/test/scala/org/json4s/native/MapSerializationExamples.scala +++ b/tests/src/test/scala/org/json4s/native/MapSerializationExamples.scala @@ -48,9 +48,7 @@ class MapSerializationExamples extends Specification { "Map with Timestamp key" in { val t2013 = new Timestamp(1356998400) val t2014 = new Timestamp(1388534400) - // TODO use Map.apply instead of "new Map.Map2" when 2.13.0-M5 released - // https://github.com/scala/scala/commit/6a570b6f1f59222cae4f55aa25d48e3d4c22ea59 - val pw: Map[Timestamp, String] = new Map.Map2(t2013, "hello", t2014, "world") + val pw: Map[Timestamp, String] = Map(t2013 -> "hello", t2014 -> "world") val ser = swrite(pw) val f2013 = formats.dateFormat.format(t2013) @@ -80,9 +78,7 @@ class MapSerializationExamples extends Specification { } "case class with custom map" in { - // TODO use Map.apply instead of "new Map.Map2" when 2.13.0-M5 released - // https://github.com/scala/scala/commit/6a570b6f1f59222cae4f55aa25d48e3d4c22ea59 - val pw = PlayerWithCustomMap("zortan", new Map.Map2("2013", "zortan13", "2014", "zortan14")) + val pw = PlayerWithCustomMap("zortan", Map("2013" -> "zortan13", "2014" -> "zortan14")) val ser = swrite(pw) val s: String = """{"name":"zortan","aliasByYear":{"2013":"zortan13","2014":"zortan14"}}""" ser must_== s diff --git a/tests/src/test/scala/org/json4s/native/SerializationExamples.scala b/tests/src/test/scala/org/json4s/native/SerializationExamples.scala index 8c0fc6497..d44d752d6 100644 --- a/tests/src/test/scala/org/json4s/native/SerializationExamples.scala +++ b/tests/src/test/scala/org/json4s/native/SerializationExamples.scala @@ -170,9 +170,7 @@ object SerializationExamples extends Specification { } "Generic Map with simple values example" in { - // TODO use Map.apply instead of "new Map.Map2" when 2.13.0-M5 released - // https://github.com/scala/scala/commit/6a570b6f1f59222cae4f55aa25d48e3d4c22ea59 - val pw = PlayerWithGenericMap("zortan", new Map.Map2("1", "asd", "a", 3)) + val pw = PlayerWithGenericMap("zortan", Map("1" -> "asd", "a" -> 3)) val ser = swrite(pw) ser must_== """{"name":"zortan","infomap":{"1":"asd","a":3}}""" read[PlayerWithGenericMap](ser) must_== pw @@ -180,9 +178,7 @@ object SerializationExamples extends Specification { "Generic Map with case class and type hint example" in { implicit val formats = native.Serialization.formats(ShortTypeHints(List(classOf[Player]))) - // TODO use Map.apply instead of "new Map.Map3" when 2.13.0-M5 released - // https://github.com/scala/scala/commit/6a570b6f1f59222cae4f55aa25d48e3d4c22ea59 - val pw = PlayerWithGenericMap("zortan", new Map.Map3("1", "asd", "a", 3, "friend", Player("joe"))) + val pw = PlayerWithGenericMap("zortan", Map("1" -> "asd", "a" -> 3, "friend" -> Player("joe"))) val ser = swrite(pw) ser must_== """{"name":"zortan","infomap":{"1":"asd","a":3,"friend":{"jsonClass":"Player","name":"joe"}}}""" read[PlayerWithGenericMap](ser) must_== pw From 397f4a138bd9fa20340fa6216b8443ec3b9a68c4 Mon Sep 17 00:00:00 2001 From: kenji yoshida <6b656e6a69@gmail.com> Date: Thu, 27 Jun 2019 11:19:50 +0900 Subject: [PATCH 03/17] remove outdated Scala 2.13 milestone build setting (cherry picked from commit ddb661903f1584840177c110538f3cfa0f3ee0c6) --- project/build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.scala b/project/build.scala index b0cae43c9..90e10cfed 100644 --- a/project/build.scala +++ b/project/build.scala @@ -92,7 +92,7 @@ object build { unmanagedSourceDirectories in scope += { val base = (sourceDirectory in scope).value.getParentFile / Defaults.nameForSrc(scope.name) CrossVersion.partialVersion(scalaVersion.value) match { - case Some((2, v)) if v >= 13 && scalaVersion.value != "2.13.0-M3" => + case Some((2, v)) if v >= 13 => base / s"scala-2.13+" case _ => base / s"scala-2.13-" From f4768edfa32248feed4412e6bf7fae9e2cb96b11 Mon Sep 17 00:00:00 2001 From: Yann Simon Date: Thu, 10 Oct 2019 09:12:08 +0200 Subject: [PATCH 04/17] avoid instantiation of list (cherry picked from commit cf6958722bd8d7f3cea8e02ed2b49b4d7f741ff0) --- ast/src/main/scala/org/json4s/JsonAST.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ast/src/main/scala/org/json4s/JsonAST.scala b/ast/src/main/scala/org/json4s/JsonAST.scala index fbd138dfe..bf88b71a8 100644 --- a/ast/src/main/scala/org/json4s/JsonAST.scala +++ b/ast/src/main/scala/org/json4s/JsonAST.scala @@ -150,7 +150,7 @@ object JsonAST { case class JObject(obj: List[JField]) extends JValue { type Values = Map[String, Any] - def values = obj.map { case (n, v) => (n, v.values) } toMap + def values = obj.iterator.map { case (n, v) => (n, v.values) }.toMap override def equals(that: Any): Boolean = that match { case o: JObject => obj.toSet == o.obj.toSet From 6317b077277c0c44259a816ad60ad0356356c209 Mon Sep 17 00:00:00 2001 From: xuwei-k <6b656e6a69@gmail.com> Date: Tue, 5 May 2020 16:13:52 +0900 Subject: [PATCH 05/17] Update sbt and travis-ci mima setting --- .travis.yml | 2 +- project/build.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1b50f753a..ac12b2271 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,7 @@ cache: before_install: - export TZ=Australia/Canberra script: - - travis_retry sbt "++${TRAVIS_SCALA_VERSION}!" test + - travis_retry sbt "++${TRAVIS_SCALA_VERSION}!" mimaReportBinaryIssues test before_cache: - find $HOME/.sbt -name "*.lock" | xargs rm - find $HOME/.ivy2 -name "ivydata-*.properties" | xargs rm diff --git a/project/build.properties b/project/build.properties index c0bab0494..797e7ccfd 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.2.8 +sbt.version=1.3.10 From 54d31c4f08602931e5a1a093d345c54f8d3ded82 Mon Sep 17 00:00:00 2001 From: kenji yoshida <6b656e6a69@gmail.com> Date: Tue, 29 Oct 2019 11:52:55 +0900 Subject: [PATCH 06/17] remove Opts.resolver.sonatypeReleases (cherry picked from commit 54757c221377fc97bd3bd2a2b1a938ffde638598) --- project/build.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/project/build.scala b/project/build.scala index 90e10cfed..527a052b3 100644 --- a/project/build.scala +++ b/project/build.scala @@ -101,7 +101,6 @@ object build { }, parallelExecution in Test := false, manifestSetting, - resolvers ++= Seq(Opts.resolver.sonatypeReleases), crossVersion := CrossVersion.binary ) From d03c05de21fa5e0b082c30efb05111990e060c24 Mon Sep 17 00:00:00 2001 From: Cosmin Stroe Date: Fri, 20 Dec 2019 01:39:28 -0600 Subject: [PATCH 07/17] Closes: #641. * Fix MappingException message when parsing missing Option field with strict option parsing enabled (cherry picked from commit 9d609bbac5ad2e27eac3ba4f2eddc764726f5da4) --- .../main/scala/org/json4s/Extraction.scala | 5 ++-- .../scala/org/json4s/ExtractionBugs.scala | 25 +++++++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/core/src/main/scala/org/json4s/Extraction.scala b/core/src/main/scala/org/json4s/Extraction.scala index c745d43b6..b9d4a59c2 100644 --- a/core/src/main/scala/org/json4s/Extraction.scala +++ b/core/src/main/scala/org/json4s/Extraction.scala @@ -563,10 +563,9 @@ object Extraction { else x } catch { case e @ MappingException(msg, _) => - if (descr.isOptional && - (!formats.strictOptionParsing || extract(json, ScalaType[Null](implicitly)) == null)) + if (descr.isOptional && !formats.strictOptionParsing) { defv(None) - else fail("No usable value for " + descr.name + "\n" + msg, e) + } else fail("No usable value for " + descr.name + "\n" + msg, e) } } } diff --git a/tests/src/test/scala/org/json4s/ExtractionBugs.scala b/tests/src/test/scala/org/json4s/ExtractionBugs.scala index 0741fd563..e570731a6 100644 --- a/tests/src/test/scala/org/json4s/ExtractionBugs.scala +++ b/tests/src/test/scala/org/json4s/ExtractionBugs.scala @@ -327,5 +327,30 @@ abstract class ExtractionBugs[T](mod: String) extends Specification with JsonMet val json = Extraction.decompose(obj) json mustEqual JObject("s" -> JString("hello"), "i" -> JInt(3)) } + + "Extract error should preserve error message when strict option parsing is enabled" in { + implicit val formats = new DefaultFormats { + override val strictOptionParsing: Boolean = true + } + + val obj = parse("""{"opt": "not an int"}""".stripMargin) + + Extraction.extract[OptionOfInt](obj) must throwA( + new MappingException( + """ + |No usable value for opt + |Do not know how to convert JString(not an int) into int + |""".stripMargin.trim)) + } + + "Extract should succeed for optional field with null value" in { + val obj = parse("""{"opt":null}""".stripMargin) + Extraction.extract[OptionOfInt](obj) must_== OptionOfInt(None) + } + + "Extract should succeed for missing optional field" in { + val obj = parse("""{}""".stripMargin) + Extraction.extract[OptionOfInt](obj) must_== OptionOfInt(None) + } } } From 8766a741d7b05c61416bff0fc6a0bb7d328195c3 Mon Sep 17 00:00:00 2001 From: "Magnolia.K" Date: Wed, 1 Jan 2020 16:52:23 +0900 Subject: [PATCH 08/17] fixed unflatten's Algorithm for identifying fields fix #638 (cherry picked from commit d96cf04a39fa30badebce09712f829ae605c6f70) --- core/src/main/scala/org/json4s/Extraction.scala | 16 +++++++++++----- .../org/json4s/ExtractionExamplesSpec.scala | 6 ++++++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/core/src/main/scala/org/json4s/Extraction.scala b/core/src/main/scala/org/json4s/Extraction.scala index b9d4a59c2..8ac3a26a1 100644 --- a/core/src/main/scala/org/json4s/Extraction.scala +++ b/core/src/main/scala/org/json4s/Extraction.scala @@ -328,15 +328,21 @@ object Extraction { else JString(ParserUtil.unquote(value.substring(1))) } - def submap(prefix: String): Map[String, String] = - map.withFilter(t => t._1.startsWith(prefix)).map( - t => (t._1.substring(prefix.length), t._2) - ) - val ArrayProp = new Regex("""^(\.([^\.\[]+))\[(\d+)\].*$""") val ArrayElem = new Regex("""^(\[(\d+)\]).*$""") val OtherProp = new Regex("""^(\.([^\.\[]+)).*$""") + def submap(prefix: String): Map[String, String] = { + map.withFilter { t => + t._1 match { + case ArrayProp(p, _, _) if p == prefix => true + case ArrayElem(p, _) if p == prefix => true + case OtherProp(p, _) if p == prefix => true + case _ => false + } + } map { t => (t._1.substring(prefix.length), t._2) } + } + val uniquePaths = map.keys.foldLeft[Set[String]](Set()) { (set, key) => key match { diff --git a/tests/src/test/scala/org/json4s/ExtractionExamplesSpec.scala b/tests/src/test/scala/org/json4s/ExtractionExamplesSpec.scala index 7115dc56f..b9fe2dcda 100644 --- a/tests/src/test/scala/org/json4s/ExtractionExamplesSpec.scala +++ b/tests/src/test/scala/org/json4s/ExtractionExamplesSpec.scala @@ -198,6 +198,12 @@ abstract class ExtractionExamples[T](mod: String, ser : json4s.Serialization) ex Extraction.unflatten(m) must_== JObject(List(JField("foo", JArray(List(JInt(0), JInt(1), JInt(2)))))) } + "Unflatten example with field name is prefix of the other field name" in { + val m = Map(".data" -> "5", ".data_type" -> "6") + + Extraction.unflatten(m) must_== JObject(JField("data", JInt(5)), JField("data_type", JInt(6))) + } + "Flatten and unflatten are symmetric" in { val parsed = parse(testJson) From 3d3856305726e3d6430d8b69ad4866c4504d0d08 Mon Sep 17 00:00:00 2001 From: Dan White Date: Sat, 11 Apr 2020 00:05:40 -0600 Subject: [PATCH 09/17] Issue #661: Downvotes on args with defaults will cause the least correct ctor to be used. (#662) (cherry picked from commit 62d87237f3d5fff2da2cbe5c03adb02b3cdde6b6) --- .../main/scala/org/json4s/reflect/descriptors.scala | 1 + .../src/test/scala/org/json4s/SerializationSpec.scala | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/core/src/main/scala/org/json4s/reflect/descriptors.scala b/core/src/main/scala/org/json4s/reflect/descriptors.scala index dbeb9f8c3..8a4cc0494 100644 --- a/core/src/main/scala/org/json4s/reflect/descriptors.scala +++ b/core/src/main/scala/org/json4s/reflect/descriptors.scala @@ -56,6 +56,7 @@ case class ClassDescriptor( args.foldLeft(0)((s, arg) => if (names.contains(arg.name)) s+1 else if (arg.isOptional) s + else if (arg.hasDefault) s else -100 ) diff --git a/tests/src/test/scala/org/json4s/SerializationSpec.scala b/tests/src/test/scala/org/json4s/SerializationSpec.scala index 38902e6bd..3e632a630 100644 --- a/tests/src/test/scala/org/json4s/SerializationSpec.scala +++ b/tests/src/test/scala/org/json4s/SerializationSpec.scala @@ -151,8 +151,16 @@ abstract class SerializationSpec(serialization: Serialization, baseFormats: Form val actual = Extraction.extract[AnotherModel](jackson.parseJson(json)) actual must_== expected } - } + "#661 Matching algorithm picks least correct ctor" in { + serialization.read[BadSpec](s"""{"item2": 789, "item3": 123}""") must_== BadSpec(789, 123) + } + } } +} +case class BadSpec(item2: Int, item3: Int, isVisited: Boolean = false) +case object BadSpec { + def apply(item1: Int, item2: Int, item3: Int): BadSpec = BadSpec(item2, item3) } + From b2b89e08aa0d6c7278d9797758f1dca8afb72c84 Mon Sep 17 00:00:00 2001 From: Arnaud Gourlay Date: Wed, 15 Apr 2020 15:02:53 +0200 Subject: [PATCH 10/17] traverse fields only once by testing existence with find (cherry picked from commit f3aeec2e2352d8d601209d3695d832cb56e89af0) --- core/src/main/scala/org/json4s/MonadicJValue.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/src/main/scala/org/json4s/MonadicJValue.scala b/core/src/main/scala/org/json4s/MonadicJValue.scala index ecbf5e444..e717778ee 100644 --- a/core/src/main/scala/org/json4s/MonadicJValue.scala +++ b/core/src/main/scala/org/json4s/MonadicJValue.scala @@ -226,8 +226,7 @@ class MonadicJValue(jv: JValue) { */ def findField(p: JField => Boolean): Option[JField] = { def find(json: JValue): Option[JField] = json match { - case JObject(fs) if fs exists p => fs find p - case JObject(fs) => fs.flatMap { case (_, v) => find(v) }.headOption + case JObject(fs) => fs.find(p).orElse(fs.flatMap { case (_, v) => find(v) }.headOption) case JArray(l) => l.flatMap(find _).headOption case _ => None } From 9b2ad1f33198c3ccea66716ae4dad3541794b378 Mon Sep 17 00:00:00 2001 From: kenji yoshida <6b656e6a69@gmail.com> Date: Sun, 26 Apr 2020 14:03:43 +0900 Subject: [PATCH 11/17] cache $HOME/.cache/coursier/v1 (cherry picked from commit 85de9b603927f983c7530b344006661f1a190632) --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index ac12b2271..c96bb1e7a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,7 @@ cache: directories: - $HOME/.ivy2/cache - $HOME/.sbt/launchers + - $HOME/.cache/coursier/v1 before_install: - export TZ=Australia/Canberra script: From 9bad35fe8444c4515fdab656cd77a1bc5e8b32d2 Mon Sep 17 00:00:00 2001 From: xuwei-k <6b656e6a69@gmail.com> Date: Tue, 5 May 2020 15:56:36 +0900 Subject: [PATCH 12/17] add org.json4s.Compat. avoid reflection (cherry picked from commit 4b2187afcbc4e03f0dfef4ccff00fb6b0b6ba394) --- .../main/scala-2.13+/org/json4s/Compat.scala | 15 ++++++++ .../main/scala-2.13-/org/json4s/Compat.scala | 11 ++++++ .../main/scala/org/json4s/Extraction.scala | 34 +++---------------- 3 files changed, 30 insertions(+), 30 deletions(-) create mode 100644 core/src/main/scala-2.13+/org/json4s/Compat.scala create mode 100644 core/src/main/scala-2.13-/org/json4s/Compat.scala diff --git a/core/src/main/scala-2.13+/org/json4s/Compat.scala b/core/src/main/scala-2.13+/org/json4s/Compat.scala new file mode 100644 index 000000000..21b9a4a99 --- /dev/null +++ b/core/src/main/scala-2.13+/org/json4s/Compat.scala @@ -0,0 +1,15 @@ +package org.json4s + +import scala.language.reflectiveCalls + +private[json4s] object Compat { + def makeCollection(clazz: Class[_], array: Array[_]): Option[Any] = { + reflect.ScalaSigReader.companions(clazz.getName).flatMap(_._2).map { c => + val builder = c + .asInstanceOf[ {def newBuilder: collection.mutable.Builder[Any, Any]}] + .newBuilder + builder ++= array + builder.result + } + } +} diff --git a/core/src/main/scala-2.13-/org/json4s/Compat.scala b/core/src/main/scala-2.13-/org/json4s/Compat.scala new file mode 100644 index 000000000..484de3450 --- /dev/null +++ b/core/src/main/scala-2.13-/org/json4s/Compat.scala @@ -0,0 +1,11 @@ +package org.json4s + +import scala.language.reflectiveCalls + +private[json4s] object Compat { + def makeCollection(clazz: Class[_], array: Array[_]): Option[Any] = { + reflect.ScalaSigReader.companions(clazz.getName).flatMap(_._2).map { + _.asInstanceOf[ {def apply(elems: collection.Seq[_]): Any}].apply(array.toSeq) + } + } +} diff --git a/core/src/main/scala/org/json4s/Extraction.scala b/core/src/main/scala/org/json4s/Extraction.scala index 8ac3a26a1..aff3a23b0 100644 --- a/core/src/main/scala/org/json4s/Extraction.scala +++ b/core/src/main/scala/org/json4s/Extraction.scala @@ -462,36 +462,10 @@ object Extraction { else if (tpe.erasure == classOf[java.util.ArrayList[_]]) mkCollection(a => new java.util.ArrayList[Any](a.toList.asJavaCollection)) else if (tpe.erasure.isArray) mkCollection(mkTypedArray) else { - def getCompanion(className: String): Option[Any] = { - val c = try { - Some(Class.forName(className).isAssignableFrom(tpe.erasure)) - } catch { - case _: ClassNotFoundException => - None - } - c.flatMap { _ => - reflect.ScalaSigReader.companions(tpe.erasure.getName).flatMap(_._2) - } - } - - import language.reflectiveCalls - - getCompanion("scala.collection.generic.GenericTraversableTemplate") match { - case Some(c) => - val companion = c.asInstanceOf[{def apply(elems: collection.Seq[_]): Any}] - mkCollection(a => companion(a.toSeq)) - case _ => - getCompanion("scala.collection.Factory") match { - case Some(c) => - val companion = c.asInstanceOf[{def newBuilder: collection.mutable.Builder[Any, Any]}] - mkCollection{ a => - val b = companion.newBuilder - b ++= a - b.result - } - case _ => - fail("Expected collection but got " + tpe) - } + mkCollection{ array => + Compat.makeCollection(tpe.erasure, array).getOrElse(fail( + "Expected collection but got " + tpe + )) } } } From 0ea50df3cae47987adb451709f177f50ed976973 Mon Sep 17 00:00:00 2001 From: xuwei-k <6b656e6a69@gmail.com> Date: Tue, 5 May 2020 16:23:01 +0900 Subject: [PATCH 13/17] Update dependencies --- .travis.yml | 6 +++--- project/Dependencies.scala | 14 +++++++------- project/plugins.sbt | 6 +++--- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.travis.yml b/.travis.yml index c96bb1e7a..71a078450 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,11 +4,11 @@ jdk: openjdk8 scala: - 2.10.7 - 2.11.12 - - 2.12.8 - - 2.13.0 + - 2.12.11 + - 2.13.2 matrix: include: - - scala: 2.12.8 + - scala: 2.12.11 jdk: openjdk11 sudo: false cache: diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 419eb7bde..27561b593 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -5,28 +5,28 @@ object Dependencies { lazy val jaxbApi = "javax.xml.bind" % "jaxb-api" % "2.3.1" % "test" lazy val jodaTime = Seq( - "joda-time" % "joda-time" % "2.10.1", - "org.joda" % "joda-convert" % "2.2.0" + "joda-time" % "joda-time" % "2.10.6", + "org.joda" % "joda-convert" % "2.2.1" ) lazy val jackson = Seq( - "com.fasterxml.jackson.core" % "jackson-databind" % "2.9.8" + "com.fasterxml.jackson.core" % "jackson-databind" % "2.9.10.4" ) - lazy val scalaz_core = "org.scalaz" %% "scalaz-core" % "7.2.27" + lazy val scalaz_core = "org.scalaz" %% "scalaz-core" % "7.2.30" lazy val paranamer = "com.thoughtworks.paranamer" % "paranamer" % "2.8" lazy val specs = Def.setting{ CrossVersion.partialVersion(scalaVersion.value) match { case Some((2, v)) if v <= 10 => Seq("org.specs2" %% "specs2-scalacheck" % "3.10.0" % "test") case _ => - Seq("org.specs2" %% "specs2-scalacheck" % "4.5.1" % "test") + Seq("org.specs2" %% "specs2-scalacheck" % "4.9.4" % "test") } } - lazy val mockito = "org.mockito" % "mockito-core" % "2.24.5" % "test" + lazy val mockito = "org.mockito" % "mockito-core" % "3.3.3" % "test" def scalaXml(scalaVersion: String) = { PartialFunction.condOpt(CrossVersion.partialVersion(scalaVersion)){ case Some((2, scalaMajor)) if scalaMajor >= 13 => - Seq("org.scala-lang.modules" %% "scala-xml" % "1.2.0") + Seq("org.scala-lang.modules" %% "scala-xml" % "1.3.0") case Some((2, scalaMajor)) if scalaMajor >= 11 => Seq("org.scala-lang.modules" %% "scala-xml" % "1.1.0") }.toList.flatten diff --git a/project/plugins.sbt b/project/plugins.sbt index 832e08c4e..532632eeb 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,8 +1,8 @@ addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.6.4") addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.9.0") -addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "2.5") -addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.2") -addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.4.1") +addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.9.2") +addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.0.1") +addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.5.0") scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature") From 6e4be49b267c1a7e245101f8c9dec08d9cf5de93 Mon Sep 17 00:00:00 2001 From: xuwei-k <6b656e6a69@gmail.com> Date: Tue, 29 Oct 2019 12:05:13 +0900 Subject: [PATCH 14/17] use sbt-release plugin (cherry picked from commit 523c3a33b8fb9a463bd48f045ca7e91ce1e7ad3f) --- build.sbt | 2 +- project/build.scala | 25 ++++++++++++++++++------- project/plugins.sbt | 1 + version.sbt | 1 + 4 files changed, 21 insertions(+), 8 deletions(-) create mode 100644 version.sbt diff --git a/build.sbt b/build.sbt index 48fc963bb..66b2a6e4d 100644 --- a/build.sbt +++ b/build.sbt @@ -1,4 +1,4 @@ -import xml.Group +import scala.xml.Group import Dependencies._ import build._ diff --git a/project/build.scala b/project/build.scala index 527a052b3..275f24ed0 100644 --- a/project/build.scala +++ b/project/build.scala @@ -3,6 +3,9 @@ import Keys._ import xml.Group import MimaSettings.mimaSettings import com.typesafe.tools.mima.plugin.MimaKeys.{mimaPreviousArtifacts, mimaReportBinaryIssues} +import sbtrelease.ReleasePlugin.autoImport._ +import sbtrelease.ReleasePlugin.autoImport.ReleaseTransformations._ +import xerial.sbt.Sonatype.autoImport._ object build { import Dependencies._ @@ -24,12 +27,7 @@ object build { } val mavenCentralFrouFrou = Seq( - publishTo := Some( - if (isSnapshot.value) - Opts.resolver.sonatypeSnapshots - else - Opts.resolver.sonatypeStaging - ), + publishTo := sonatypePublishToBundle.value, homepage := Some(new URL("https://github.com/json4s/json4s")), startYear := Some(2009), licenses := Seq(("Apache-2.0", new URL("http://www.apache.org/licenses/LICENSE-2.0"))), @@ -86,7 +84,6 @@ object build { Seq("-Ywarn-unused:imports") } }, - version := "3.6.8-SNAPSHOT", javacOptions ++= Seq("-target", "1.8", "-source", "1.8"), Seq(Compile, Test).map { scope => unmanagedSourceDirectories in scope += { @@ -99,6 +96,20 @@ object build { } } }, + releaseProcess := Seq[ReleaseStep]( + checkSnapshotDependencies, + inquireVersions, + runClean, + runTest, + setReleaseVersion, + commitReleaseVersion, + tagRelease, + releaseStepCommandAndRemaining("+publishSigned"), + releaseStepCommandAndRemaining("sonatypeBundleRelease"), + setNextVersion, + commitNextVersion, + pushChanges + ), parallelExecution in Test := false, manifestSetting, crossVersion := CrossVersion.binary diff --git a/project/plugins.sbt b/project/plugins.sbt index 532632eeb..a9d8edd7e 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -3,6 +3,7 @@ addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.9.0") addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.9.2") addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.0.1") addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.5.0") +addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.13") scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature") diff --git a/version.sbt b/version.sbt new file mode 100644 index 000000000..86162655a --- /dev/null +++ b/version.sbt @@ -0,0 +1 @@ +version in ThisBuild := "3.6.8-SNAPSHOT" From c2e9fedbe056e82da2baaf7fc28a9ad7a33dc85d Mon Sep 17 00:00:00 2001 From: xuwei-k <6b656e6a69@gmail.com> Date: Tue, 5 May 2020 16:44:29 +0900 Subject: [PATCH 15/17] Setting version to 3.6.8 --- version.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.sbt b/version.sbt index 86162655a..2d34694f2 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "3.6.8-SNAPSHOT" +version in ThisBuild := "3.6.8" From d0fdd64b2c9023dbf29bada102dc8c7ee9c60394 Mon Sep 17 00:00:00 2001 From: Peter Zhong Date: Thu, 28 Oct 2021 11:22:51 -0500 Subject: [PATCH 16/17] Update build.scala --- project/build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.scala b/project/build.scala index 6aa2c6981..77022451f 100644 --- a/project/build.scala +++ b/project/build.scala @@ -83,7 +83,7 @@ object build { Seq("-Ywarn-unused:imports") } }, - version := "3.6.7-livongo-1.0.0", + version := "3.6.7-livongo-1.0.1-belplat-172", javacOptions ++= Seq("-target", "1.8", "-source", "1.8"), Seq(Compile, Test).map { scope => unmanagedSourceDirectories in scope += { From fc7f2c4c108780d2ea42b7b021be66f0db80d687 Mon Sep 17 00:00:00 2001 From: Peter Zhong Date: Thu, 28 Oct 2021 13:52:08 -0500 Subject: [PATCH 17/17] fix merge issues --- core/src/main/scala/org/json4s/Extraction.scala | 5 ++--- project/build.scala | 17 ++--------------- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/core/src/main/scala/org/json4s/Extraction.scala b/core/src/main/scala/org/json4s/Extraction.scala index 05ff959b6..84ad16fc8 100644 --- a/core/src/main/scala/org/json4s/Extraction.scala +++ b/core/src/main/scala/org/json4s/Extraction.scala @@ -543,8 +543,7 @@ object Extraction { else x } catch { case e @ MappingException(msg, _) => - if (descr.isOptional && - (!(formats.strictOptionParsing || formats.strictOptionParsingPre36) || extract(json, ScalaType[Null](implicitly)) == null)) + if (descr.isOptional && (!(formats.strictOptionParsing || formats.strictOptionParsingPre36) || extract(json, ScalaType[Null](implicitly)) == null)) { defv(None) } else fail("No usable value for " + descr.name + "\n" + msg, e) } @@ -749,4 +748,4 @@ object Extraction { private[this] def formatDate(s: String, formats: Formats): Date = { formats.dateFormat.parse(s).getOrElse(fail("Invalid date '" + s + "'")) } -} +} \ No newline at end of file diff --git a/project/build.scala b/project/build.scala index e08a1bddd..9a6f7fceb 100644 --- a/project/build.scala +++ b/project/build.scala @@ -84,7 +84,7 @@ object build { Seq("-Ywarn-unused:imports") } }, - version := "3.6.7-livongo-1.0.1-belplat-172", + version := "3.6.8-livongo-1.0.2-belplat-172", javacOptions ++= Seq("-target", "1.8", "-source", "1.8"), Seq(Compile, Test).map { scope => unmanagedSourceDirectories in scope += { @@ -97,22 +97,9 @@ object build { } } }, - releaseProcess := Seq[ReleaseStep]( - checkSnapshotDependencies, - inquireVersions, - runClean, - runTest, - setReleaseVersion, - commitReleaseVersion, - tagRelease, - releaseStepCommandAndRemaining("+publishSigned"), - releaseStepCommandAndRemaining("sonatypeBundleRelease"), - setNextVersion, - commitNextVersion, - pushChanges - ), parallelExecution in Test := false, manifestSetting, + resolvers ++= Seq(Opts.resolver.sonatypeReleases), crossVersion := CrossVersion.binary )