Introduction ============ Declarative Stream Mapping(DSM) is a *stream* deserializer library that makes parsing of **XML and JSON** easy. DSM allows you to make custom parsing, filtering, transforming, aggregating, grouping on any JSON or XML document at stream time(read only once). DSM uses yaml or json for configuration definitions **If you parsing a complex, huge file and want to have high performance and low memory usage then DSM is for you.** Simple Example =============== **Lets Parse below simple JSON and XML file with DSM** File contents are taken from `Swagger Petstore example `_. Slightly changed. **Source file** .. content-tabs:: .. tab-container:: tab1 :title: JSON .. code-block:: json { "id": 1, "name": "Van Kedisi", "status": "sold", "createDate": "01/24/2019", "category": {"id": 1,"name": "Cats"}, "tags": [ {"id": 1,"name": "Cute"}, {"id": 2,"name": "Popular"} ], "photoUrls": ["url1","url2" ] } .. tab-container:: tab2 :title: XML .. code-block:: xml Van Kedisi sold 01/24/2019 1 Cats 1 Cute 2 Popular url1 url2 **Those are rules that we want to apply during parsing**. - exclude "photoUrls" tag. - read only "name" field of "tags" tag. - read only "name" field of "category" tag. - **add new the "isPopular" field that it's value is true, if "tag.name" has "Popular" value** **DSM config file** [YAML] .. code-block:: yaml params: dateFormat: MM/dd/yyyy result: type:object path: / xml: path: /Pet filter: $self.data.status=='sold' fields: id: dataType: int xml: attribute: true name: string status: status createDate: date category: path: category/name isPopular: default: $self.data.tags.contains("Popular") tags: type:array path: tags/name |tags/tag/name # this is a regex expression. works for both JSON and XML **Class to deserialize** [JAVA] .. code-block:: java public class Pet { private int id; private String name; private boolean isPopular; private String status; private String category; private Date createDate; private List tags; // getter/setter } **Read Data** .. code-block:: java DSMBuilder builder = new DSMBuilder("dsm-config-file.yaml"); DSM dsm = builder.setType(DSMBuilder.XML).create(); Pet pet = dsm.toObject(new File("path/to/xmlFile.xml"),Pet.class); // read data from xml file dsm = builder.setType(DSMBuilder.JSON).create(); pet = dsm.toObject(new File("path/to/jsonFile.json"),Pet.class); // read data from json file Features ============== - **Work** for both **XML** and **JSON** - **Custom stream parsing** - **Filtering** by value on any field with very **low cognitive complexity** - Flexible value **transformation**. - **Default value assignment** - Custom **function calling** during parsing - **Powerful Scripting**(`Apache JEXL `_, Groovy, Javascript and other jsr223 implementations are supported) - **Multiple inheritance** between DSM config file (DSM file can **extends to another config file**) - **Reusable fragments support** - Very **short learning curve** - **Memory** and **CPU** efficient - **Partial data extraction** from JSON or XML - **String manipulation** with expression Installation ============== .. content-tabs:: .. tab-container:: tab1 :title: Maven **Jackson** .. code-block:: xml com.github.mfatihercik dsm 1.0.4 .. tab-container:: tab2 :title: Gradle **Jackson** .. code-block:: xml compile ('com.github.mfatihercik:dsm:1.0.4') Sample Config File =================== Detailed documentation and all option is `here `_. This config file contains some possible option and their short description. [header.yaml] .. code-block:: yaml params: dateFormat: MM/dd/yyyy # define date format for "date" data type transformations: SOLD_STATUS: # value transformation for "isAvailable" property map: sold: false pending: false available: true DEFAULT: false SOLD_STATUS_SKIP: $ref: $transformations.SOLD_STATUS # extends to "SOLD_STATUS" transformation. map: DEFAULT: exclude # exclude default value onlyIfExist: # make transformation only source value exist in transformation map other wise return as it is functions: insertPet: com.example.InsertPet # declare a function to declare at Parsing Element. fragments: # create reusable fragment category: type:object fields: id: int name: string type: string [main.yaml] .. code-block:: yaml $extends: header.yaml # extends to header.yaml config. result: type:array # result is an array path: / | /Pets/Pet # start reading form beginning for json. path is a regex. we can define both for xml and json same time. or we can declare for xml in XML field. xml: path: /Pets/Pet # start reading from /Pets/Pet for xml filter: $self.data.isAvailable # filter by "isAvailable" property. "self" key word refers to current Node. self.parent refers to parent Node. self.data refers to current node data function: insertPet # call "insertPet" function for every element of "result" array fields: name: string # read name as string. id: dataType: int # read id as int xml: attribute: true # id is an attribute on /Pets/Pet tag. createDate: date # use dateFormat in params then convert string to date isAvailable: path: status # read isAvailable as string from "status" tag dataType: boolean transformationCode: SOLD_STATUS # user "SOLD_STATUS" transformation to map from "status" to "isAvailable" category: $ref: $fragments.category # extends to "fragment.category" fields: type: exclude # exclude "type" field from "category" fragment name: default: 'Animal' #set default value to 'Animal' if "category/name" tag not exist in source document isPopular: default: $self.data.tags.contains("Popular") # set default value of "isPopular" property tags: type:array path: tags/name filter: $value.length>15 # filter by length of value. xml: path: tags/tag/name